diff options
Diffstat (limited to 'distribution')
44 files changed, 5706 insertions, 0 deletions
diff --git a/distribution/virt/import/Makefile b/distribution/virt/import/Makefile new file mode 100644 index 0000000..4c718bb --- /dev/null +++ b/distribution/virt/import/Makefile @@ -0,0 +1,124 @@ +# The toplevel namespace within which the test lives. +TOPLEVEL_NAMESPACE=/distribution + +# The name of the package under test: +PACKAGE_NAME=virt + +# The path of the test below the package: +RELATIVE_PATH=import + +# Preserve the RPM name from the old repo location: +export RHTS_RPM_NAME=distribution-distribution-virt-import + +# Version of the Test. Used with make tag. +export TESTVERSION=4.0 + +# The compiled namespace of the test. +export TEST=$(TOPLEVEL_NAMESPACE)/$(PACKAGE_NAME)/$(RELATIVE_PATH) + +# Define to copy files +define cpfile + echo "Chmod a+x $(1)" ; \ + chmod a+x ./$(1) ; \ + echo "Copying $(1)" ; \ + cp ./$(1) /usr/local/bin/. ; \ + echo "Restore SELinux context $(1)" ; \ + restorecon -vv /usr/local/bin/$(1) ; +endef + +BINFILES= guestcheck4down \ + guestcheck4up \ + guestgethostname \ + startguest \ + stopguest \ + virshstartguest \ + virshstopguest \ + wait4shutdown \ + wait4login \ + wait4guest \ + xmstopguest \ + xmstartguest \ + getguestnames.sh \ + isparavirt \ + ishvm \ + isxen \ + iskvm \ + isptyconsole \ + isfileconsole \ + filetopty.sh \ + ptytofile.sh \ + getconsolefile \ + get_guest_info.py \ + get_guest_fqdn.py \ + rhts_submit_virt_logs \ + rhts_virt_funcs.sh \ + guest1cmd \ + scp.exp \ + zrhel5_write_consolelogs.py \ + lxml_guestname_resolution.py \ + minidom_guestname_resolution.py \ + wait4guesttasks + +FILES= $(METADATA) \ + $(BINFILES) \ + runtest.sh \ + Makefile \ + get_guest_info.py \ + get_guest_fqdn.py \ + start_recipe.py \ + logguestconsoles.c \ + logguestconsoles.initd \ + zrhel5_write_consolelogs.initd \ + iptables_after_libvirtd.initd + +binsetup: + $(foreach f,$(BINFILES),$(call cpfile,$(f))) + +run: binsetup + chmod a+x ./runtest.sh ./*.exp ./*.py + ./runtest.sh + +# Include Common Makefile +include /usr/share/rhts/lib/rhts-make.include + +# Generate the testinfo.desc here: +$(METADATA): + @touch $(METADATA) + @echo "Owner: Beaker Developers <beaker-devel@lists.fedorahosted.org>" > $(METADATA) + @echo "Name: $(TEST)" >> $(METADATA) + @echo "Path: $(TEST_DIR)" >> $(METADATA) + @echo "TestVersion: $(TESTVERSION)" >> $(METADATA) + @echo "Description: Test to install xen/kvm guests. Uses libvirt userspace tools." >> $(METADATA) + @echo "TestTime: 43200" >> $(METADATA) + @echo "Requires: $(PACKAGE_NAME)" >> $(METADATA) + @echo "Architectures: i386 x86_64 ia64" >> $(METADATA) + @echo "Priority: Manual" >> $(METADATA) + @echo "Requires: autofs" >> $(METADATA) + @echo "Requires: bridge-utils" >> $(METADATA) + @echo "Requires: expect" >> $(METADATA) + @echo "Requires: genisoimage" >> $(METADATA) + @echo "Requires: kernel-xen" >> $(METADATA) + @echo "Requires: kvm" >> $(METADATA) + @echo "Requires: libvirt" >> $(METADATA) + @echo "Requires: mkisofs" >> $(METADATA) + @echo "Requires: openssh-server" >> $(METADATA) + @echo "Requires: python-virtinst" >> $(METADATA) + @echo "Requires: virt-manager" >> $(METADATA) + @echo "Requires: virt-viewer" >> $(METADATA) + @echo "Requires: vnc" >> $(METADATA) + @echo "Requires: vnc-server" >> $(METADATA) + @echo "Requires: xen" >> $(METADATA) + @echo "Requires: xorg-x11-server-Xvfb" >> $(METADATA) + @echo "Requires: xmlrpc-c-devel" >> $(METADATA) + @echo "Requires: openssl-devel" >> $(METADATA) + @echo "Requires: libcurl-devel" >> $(METADATA) + @echo "Requires: libxml2-devel" >> $(METADATA) + @echo "Requires: @virtualization" >> $(METADATA) + @echo "Requires: @virtualization-platform" >> $(METADATA) + @echo "Requires: @virtualization-tools" >> $(METADATA) + @echo "Requires: @virtualization-client" >> $(METADATA) + @echo "Requires: @x11" >> $(METADATA) + @echo "Requires: @fonts" >> $(METADATA) + @echo "RunFor: xen-libs" >> $(METADATA) + @echo "RhtsOptions: -Compatible -CompatService" >> $(METADATA) + @echo "License: GPLv2" >> $(METADATA) diff --git a/distribution/virt/import/PURPOSE b/distribution/virt/import/PURPOSE new file mode 100644 index 0000000..4f7d0ce --- /dev/null +++ b/distribution/virt/import/PURPOSE @@ -0,0 +1,81 @@ +This test is used to install guests on the dom0 host it's executing on. It +doesn't need any arguments as the arguments are provided to it by the lab +controller when the job has guestrecipe . This testis required to install the +guest machines defined in the guestrecipes. + +For more information on how to write and execute virtualization testcases refer +to the Virtualization workflow information in Beaker's documentation: + http://beaker-project.org/docs/user-guide/virtualization-workflow.html + +Guest console logs: +For RHEL5 hypervisors, the guest console forwarding will be set up, unless the +tester passes on "NORHEL5CONSOLELOGS" parameter to the test. If this +environment variable is set, then the serial console forwarding won't be set. + + Currently for rhel6 and over, it'll be set up during the installation time +that all the console consoles will be forwarded to a file. This file will be at +/mnt/tests/distribution/virt/install/guests/${guestname}/logs/${guestname}_console.log + +If the tester wants to login to the system and connect to the serial console of +the guest, s/he will have to change the guest's configuration not to forward the +serial console to the file. There are a couple scripts provided for this +purpose, and they are: + filetopty.sh: cancel out the file forwarding and free up the console of the +guest, so that one can connect it via "virsh console $guestname" + ptytofile.sh: The reverse of above. + +For the guests in rhel6 hosts, we had forwarded the serial console +to a file output, but that had blocked the console access and required manual +invertention if you wanted to change it to console login access via virsh +console $guest and they were always mutually exclusive. Now you can have both by +default. It'll still be forwarding serial console output to a file and upload it +but it'll also allow you login to ttyS1 console via virsh console $guestname. By +default virsh console tries to login to ttyS0, so to you have to provide it +correct devname with: + virsh console $guestname --devname $devname + +To find out the devname just do virsh dumpxml $guestname and see what the name +is for the pty console. For example: + $ virsh dumpxml i386_kvm + ... + <serial type='pty'> + <source path='/dev/pts/1'/> + <target port='1'/> + <alias name='serial1'/> + </serial> + ... + +So for this instance you'd do : + virsh console i386_kvm --devname serial1 + +One caveat is that, we have rhts-compat service that's run and started up by +beah, which grabs the console so the console i/o is garbled and unusable. To +mitigate this, add a task in your guest recipes after /distribution/install to +stop and disable the rhts-compat service. +If you are doing this manually, you can reboot the guest in single mode and +disable the service yourself using the console. + +============================================================================== +Networking: +By default this test sets up a bridge and uses bridged network for kvm guests. +Multiple nics can be specified with "--network model=name" in guestargs, for +example, "--network model=e1000 --network model=virtio" will install a guest +with two nics, one e1000 the other virtio driver. + +============================================================================== + + +Guest domain names: + /distribution/virt/start job will be editing host's + /etc/hosts file to have the guestname resolve to its respective IP. So, in the +above instance, you can just do: + ssh root@i386_kvm +and you should be in the guest. +=============================================================================== +Test execution synchronization between the guests and hypervisor.. +In the case of a need to wait some specific tasks in a guest to execute before +running some tasks in the hypervisor, wait4guesttasks script, provided in this +task, can be used. For more information refer to the script itself. + + + diff --git a/distribution/virt/import/filetopty.sh b/distribution/virt/import/filetopty.sh new file mode 100755 index 0000000..49473ef --- /dev/null +++ b/distribution/virt/import/filetopty.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +if [[ $# != 1 ]]; then + echo "Usage: $0 [guestname] " + exit 1 +fi + +# if the guest isn't shutdown, shut it down.. try at least 2 times +guestup=0 +i=0 +while (( $i < 2 )) ; +do + if virsh list | grep -w $1; then + guestup=1 + if ! virsh shutdown $1 ; then + echo "problem with virsh shutdown $1" + exit 1 + fi + sleep 90 + fi + let "i=${i}+1" +done + +if virsh list | grep -w $1; then + echo "the guest can't be brought down" + exit 1 +fi + +if ! virsh dumpxml $1 > $1.xml ; then + echo "problem with virsh dumpxml $1" + exit 1 +fi + +sed -n ' +# if the first line copy the pattern to the hold buffer +1h +# if not the first line then append the pattern to the hold buffer +1!H +# if the last line then ... +$ { + # copy from the hold to the pattern buffer + g + # do the search and replace +# <serial type='pty'> +# <target port='0'/> +# </serial> +# <console type='pty'> +# <target port='0'/> +# </console> +# + s/<serial type='\''file'\''>.*<\/console>/<serial type='\''pty'\''>\ + <target port='\''0'\''\/>\ + <\/serial>\ + <console type='\''pty'\''>\ + <target port='\''0'\''\/>\ + <\/console>/g + # print + p +} +' $1.xml > $1.xml.tmp ; + +#redefine the guest with the edited xml +if ! virsh define ./$1.xml.tmp; then + echo "problem with virsh define ./$1.xml.tmp" + exit 1 +fi + +if [[ ${guestup} == 1 ]]; then + if ! virsh start $1; then + echo "problem restarting guest $1 " + exit 1 + fi + # give it some time to start up. + sleep 90 + + if ! virsh list | grep -w $1 ; then + echo "guest doesn't seem to be up after restart" + exit 1 + fi +fi diff --git a/distribution/virt/import/get_guest_fqdn.py b/distribution/virt/import/get_guest_fqdn.py new file mode 100755 index 0000000..9709fe2 --- /dev/null +++ b/distribution/virt/import/get_guest_fqdn.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +import os +import sys +import xmlrpclib +import xml.dom.minidom +import time + +if len(sys.argv) is not 2: + print "Usage: %s RECIPEID" % __file__ + sys.exit(1) +recipeid = sys.argv[1] +try: + int(recipeid) +except: + print "RECIPEID must be an integer" + sys.exit(1) + +xml_tries = 1 +interval = 300 +proxy = xmlrpclib.ServerProxy('http://%s:8000/RPC2' % os.environ['LAB_CONTROLLER']) +while xml_tries < 5: + try: + recipe_xml = proxy.get_my_recipe(dict(recipe_id=recipeid)) + break + except: + print "Couldn't get guestinfo from %s . sleeping %i secs" % (os.environ['LAB_CONTROLLER'] , interval) + time.sleep(interval) + xml_tries += 1 + +if xml_tries == 5: + print "Can't get guestinfo from %s" % os.environ['LAB_CONTROLLER'] + sys.exit(1) + + +doc = xml.dom.minidom.parseString(recipe_xml) + +for guestrecipe in doc.getElementsByTagName('guestrecipe'): + print guestrecipe.getAttribute('system') diff --git a/distribution/virt/import/get_guest_info.py b/distribution/virt/import/get_guest_info.py new file mode 100755 index 0000000..b184626 --- /dev/null +++ b/distribution/virt/import/get_guest_info.py @@ -0,0 +1,44 @@ +#!/usr/bin/python + +import os +import sys +import xmlrpclib +import xml.dom.minidom +import time + +xml_tries = 1 +interval = 300 +proxy = xmlrpclib.ServerProxy('http://%s:8000/RPC2' % os.environ['LAB_CONTROLLER']) +while xml_tries < 5: + try: + recipe_xml = proxy.get_my_recipe(dict(recipe_id=os.environ['RECIPEID'])) + break + except: + print "Couldn't get guestinfo from %s . sleeping %i secs" % (os.environ['LAB_CONTROLLER'] , interval) + time.sleep(interval) + xml_tries += 1 + +if xml_tries == 5: + print "Can't get guestinfo from %s" % os.environ['LAB_CONTROLLER'] + sys.exit(1) + + +doc = xml.dom.minidom.parseString(recipe_xml) + +if len(sys.argv) >= 2 and sys.argv[1] == '--kvm-num': # this is kind of a hack... + num = len(doc.getElementsByTagName('guestrecipe')) + kvm_num = len([guestrecipe for guestrecipe in doc.getElementsByTagName('guestrecipe') + if '--kvm' in guestrecipe.getAttribute('guestargs')]) + if kvm_num and kvm_num < num: + sys.exit(2) + print kvm_num + sys.exit(0) + +for guestrecipe in doc.getElementsByTagName('guestrecipe'): + print ' '.join([ + guestrecipe.getAttribute('id') or 'RECIPEIDMISSING', + guestrecipe.getAttribute('guestname') + or 'guestrecipe%s' % guestrecipe.getAttribute('id'), + guestrecipe.getAttribute('mac_address') or 'RANDOM', + guestrecipe.getAttribute('guestargs'), + ]) diff --git a/distribution/virt/import/getconsolefile b/distribution/virt/import/getconsolefile new file mode 100755 index 0000000..6a783e5 --- /dev/null +++ b/distribution/virt/import/getconsolefile @@ -0,0 +1,17 @@ +#!/bin/bash + +if [[ $# != 1 ]]; then + echo "Usage: $0 [guestname]" + exit 1 +fi + +if ! virsh list --all | grep -q $1; then + echo "guest $1 doesn't seem to exist" + exit 1 +fi + +if ! virsh dumpxml $1 | grep -A 3 "<serial type='file'>" | grep "source path=" | awk -F"'" '{print $2}'; then + exit 1 +else + exit 0 +fi diff --git a/distribution/virt/import/getguestnames.sh b/distribution/virt/import/getguestnames.sh new file mode 100755 index 0000000..fe2a007 --- /dev/null +++ b/distribution/virt/import/getguestnames.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# +# this script prints out a comma-delimited list of the name of the virtual +# guests that should be installed on this machine. +# + +RESULT="" +for dir in /mnt/tests/distribution/virt/install/guests/* ; do + guest_name=$(basename $dir) + RESULT="${guest_name}"",""${RESULT}" +done +RESULT=${RESULT%,} +echo $RESULT +exit 0 + diff --git a/distribution/virt/import/guest1cmd b/distribution/virt/import/guest1cmd new file mode 100755 index 0000000..c22c0dc --- /dev/null +++ b/distribution/virt/import/guest1cmd @@ -0,0 +1,70 @@ +#!/usr/bin/expect + +if { $argc != 2 } { + send_user "Usage: $argv0 guestname cmd\n"; + exit 1; +} +set guestname [lindex $argv 0] +set cmd [lindex $argv 1] +set prompt "\n(.*?(%|#|\\\$)) $" +set user "root" +set pass "rhts" +set timeout 30 +set i 0 +log_user 0 +#exp_internal 1 + +spawn virsh console $guestname +sleep 3 +send "\n" +send "\n" +expect { + -exact "login: " { send "$user\n"; exp_continue; } + -re "(P|p)assword:" { send "$pass\n"; sleep 10; send } + timeout { send } + eof { } +} +spawn virsh console $guestname +send "\n" +send "\n" +sleep 5 +expect { + -re $prompt { + set thelist [split $expect_out(0,string) "\n" ] + set theprompt [string trim [lindex $thelist end]] + send "$cmd\n"; send ; + } + + timeout { + if { $i < 3 } { + send "\n" + incr i + exp_continue + } else { + send_user "didn't get prompt in $timeout seconds \n"; + exit 1; + } + } + eof { + send_user "Unexpected EOF ..\n"; + exit 1; + } +} + +spawn virsh console $guestname +expect { + -re $prompt { + set idx [string first "$theprompt" $expect_out(1,string)] + set out [string range $expect_out(1,string) 0 [expr $idx - 1]] + send_user "${out}" + } + timeout { + send_user "timeout waiting for the prompt\n" + } + eof { + send_user "eof waiting for the prompt\n" + } +} + +exit 0 + diff --git a/distribution/virt/import/guestcheck4down b/distribution/virt/import/guestcheck4down new file mode 100755 index 0000000..1199992 --- /dev/null +++ b/distribution/virt/import/guestcheck4down @@ -0,0 +1,12 @@ +#!/bin/bash + +if [[ $# < 1 ]]; then + echo "Usage: $0 guestname" + exit 1 +fi + +if ! virsh list | grep -q $1; then + exit 0 +else + exit 1 +fi diff --git a/distribution/virt/import/guestcheck4up b/distribution/virt/import/guestcheck4up new file mode 100755 index 0000000..5d31065 --- /dev/null +++ b/distribution/virt/import/guestcheck4up @@ -0,0 +1,12 @@ +#!/bin/bash + +if [[ $# < 1 ]]; then + echo "Usage: $0 guestname" + exit 1 +fi + +if virsh list | grep -q $1; then + exit 0 +else + exit 1 +fi diff --git a/distribution/virt/import/guestgethostname b/distribution/virt/import/guestgethostname new file mode 100755 index 0000000..85c5ef1 --- /dev/null +++ b/distribution/virt/import/guestgethostname @@ -0,0 +1,55 @@ +#!/usr/bin/expect + +if { $argc != 1 } { + send_user "Usage: $argv0 guestname \n"; + exit 1; +} +set guestname [lindex $argv 0] +set prompt "\n(.*?(%|#|\\\$)) $" +set user "root" +set pass "rhts" +set i 0 +log_user 0 + +spawn virsh console $guestname +expect { + -exact "login: " { send "$user\n"; exp_continue; } + -re "(P|p)assword:" { send "$pass\n"; exp_continue; } + -re $prompt { + set thelist [split $expect_out(0,string) "\n" ] + set theprompt [string trim [lindex $thelist end]] + send "hostname\n"; + } + + timeout { + if { $i < 2 } { + send "\n" + incr i + exp_continue + } else { + send_user "didn't get prompt in $timeout seconds \n"; + exit 1; + } + } + eof { + send_user "Unexpected EOF ..\n"; + exit 1; + } +} + +expect { + -re $prompt { + set idx [string first "$theprompt" $expect_out(1,string)] + set host [string range $expect_out(1,string) 0 [expr $idx - 1]] + send_user "${host}" + } + timeout { + send_user "timeout waiting for the prompt\n" + } + eof { + send_user "eof waiting for the prompt\n" + } +} + +exit 0 + diff --git a/distribution/virt/import/iptables_after_libvirtd.initd b/distribution/virt/import/iptables_after_libvirtd.initd new file mode 100755 index 0000000..b5257b9 --- /dev/null +++ b/distribution/virt/import/iptables_after_libvirtd.initd @@ -0,0 +1,41 @@ +#!/bin/sh +# +# chkconfig: 2345 98 99 +# description: Agent for reporting virtual guest IDs to subscription-manager + +### BEGIN INIT INFO +# Provides: iptables_after_libvirtd +# Required-Start: $network libvirtd +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop iptables_after_libvirtd +# Description: Agent for reporting virtual guest IDs to subscription-manager +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +start() { + iptables -I INPUT -p tcp --dport 8000 -j ACCEPT + iptables -I INPUT -p udp --dport 8000 -j ACCEPT +} + +stop() { + echo "iptables_after_libvirtd." +} + +case "$1" in + start) + start + RETVAL=$? + ;; + stop) + stop + RETVAL=$? + ;; +esac +exit $RETVAL + diff --git a/distribution/virt/import/isfileconsole b/distribution/virt/import/isfileconsole new file mode 100755 index 0000000..d9da20b --- /dev/null +++ b/distribution/virt/import/isfileconsole @@ -0,0 +1,18 @@ +#/bin/bash +# + +if [[ $# != 1 ]]; then + echo "Usage: $0 [guestname]" + exit 1 +fi + +if ! virsh dumpxml $1; then + echo "problem with virsh dumpxml $1" + exit 1 +fi + +if virsh dumpxml $1 | grep -q "<serial type='file'>"; then + exit 0 +else + exit 1 +fi diff --git a/distribution/virt/import/ishvm b/distribution/virt/import/ishvm new file mode 100755 index 0000000..2ad7d9f --- /dev/null +++ b/distribution/virt/import/ishvm @@ -0,0 +1,16 @@ +#!/bin/bash +# +# script to determine if it's an hvm guest +# returns 0 if it is, non-zero if it is not. +# + +if [[ $# != 1 ]]; then + echo "Need a guest name as an argument" + exit 1; +fi + +if virsh dumpxml $1 | grep -q '>hvm<'; then + exit 0 +else + exit 1 +fi diff --git a/distribution/virt/import/iskvm b/distribution/virt/import/iskvm new file mode 100755 index 0000000..284d97b --- /dev/null +++ b/distribution/virt/import/iskvm @@ -0,0 +1,11 @@ +#!/bin/bash +# +# a stupid script to determine is we're running on kvm or not +# returns zero if it's, non-zero if it's not +# + +if lsmod | grep -q kvm; then + exit 0 +else + exit 1 +fi diff --git a/distribution/virt/import/isparavirt b/distribution/virt/import/isparavirt new file mode 100755 index 0000000..59ad35b --- /dev/null +++ b/distribution/virt/import/isparavirt @@ -0,0 +1,16 @@ +#!/bin/bash +# +# script to determine if it's paravirt guest +# returns 0 if it is, non-zero if it is not. +# + +if [[ $# != 1 ]]; then + echo "Need a guest name as an argument" + exit 1; +fi + +if virsh dumpxml $1 | grep -q '>hvm<'; then + exit 1 +else + exit 0 +fi diff --git a/distribution/virt/import/isptyconsole b/distribution/virt/import/isptyconsole new file mode 100755 index 0000000..1361085 --- /dev/null +++ b/distribution/virt/import/isptyconsole @@ -0,0 +1,18 @@ +#/bin/bash +# + +if [[ $# != 1 ]]; then + echo "Usage: $0 [guestname]" + exit 1 +fi + +if ! virsh dumpxml $1; then + echo "problem with virsh dumpxml $1" + exit 1 +fi + +if virsh dumpxml $1 | grep -q "<serial type='pty'>"; then + exit 0 +else + exit 1 +fi diff --git a/distribution/virt/import/isxen b/distribution/virt/import/isxen new file mode 100755 index 0000000..44c2650 --- /dev/null +++ b/distribution/virt/import/isxen @@ -0,0 +1,11 @@ +#!/bin/bash +# +# a stupid script to determine is the system is running on xen kernel or not +# returns zero if it is, non-zero if it's not.. + +if uname -r | grep -q xen; then + exit 0 +else + exit 1 +fi + diff --git a/distribution/virt/import/log_guest.exp b/distribution/virt/import/log_guest.exp new file mode 100755 index 0000000..9c92427 --- /dev/null +++ b/distribution/virt/import/log_guest.exp @@ -0,0 +1,22 @@ +#!/usr/bin/expect + +if { $argc != 1 } { + send_user "Usage: $argv0 <guestname> \n"; + exit 1; +} + +set timeout -1 +set guestname [lindex $argv 0] +set logfile [open /var/log/xen/console/guest-${guestname}.log a] +fconfigure $logfile -buffering line +while {1} { + + spawn virsh console $guestname + set console_spawn $spawn_id + expect { + -i $console_spawn .* { puts $logfile $expect_out(buffer); exp_continue; } + -i $console_spawn eof { sleep 3; wait -i $console_spawn } + } + +} + diff --git a/distribution/virt/import/logguestconsoles.c b/distribution/virt/import/logguestconsoles.c new file mode 100644 index 0000000..d9e3e2e --- /dev/null +++ b/distribution/virt/import/logguestconsoles.c @@ -0,0 +1,945 @@ +/* + * yum -y install xmlrpc-c-devel + * gcc -g -O0 -Wall logguestconsoles.c -o logguestconsoles -lssl $(xmlrpc-c-config client --libs) \ + * -lcurl `pkg-config libvirt --libs` `xml2-config --cflags` `xml2-config --libs` + * + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <unistd.h> +#include <ctype.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <signal.h> +#include <libgen.h> +#include <regex.h> +#include <sys/inotify.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <openssl/evp.h> +#include <xmlrpc-c/base.h> +#include <xmlrpc-c/client.h> +#include <curl/curl.h> +#include <libxml/parser.h> +#include <libxml/xmlmemory.h> +#include <libxml/tree.h> + +#define EVENT_SIZE ((sizeof(struct inotify_event)+FILENAME_MAX)*512) +#define BUF_SIZE 4096 +#define PANIC_REGEX "(Kernel panic|Oops|general protection fault|general protection handler: wrong gs|\\(XEN\\) Panic)" +#define STDOUT_REDIRECT "/var/log/logguestconsoles.out" +#define STDERR_REDIRECT "/var/log/logguestconsoles.err" + +#define DEBUG(X,fmt,...) if (DEBUG_LEVEL >= X ) { fprintf(stderr, "DEBUG%d : %s, %d " fmt "\n", X, __FILE__, __LINE__, __VA_ARGS__ ); } +#define DEBUG0(fmt,...) DEBUG(0,fmt,__VA_ARGS__) +#define DEBUG1(fmt,...) DEBUG(1,fmt,__VA_ARGS__) +#define DEBUG2(fmt,...) DEBUG(2,fmt,__VA_ARGS__) +#define DEBUG3(fmt,...) DEBUG(3,fmt,__VA_ARGS__) +#define DEBUG4(fmt,...) DEBUG(4,fmt,__VA_ARGS__) +#define DEBUG5(fmt,...) DEBUG(5,fmt,__VA_ARGS__) +#define DEBUG6(fmt,...) DEBUG(6,fmt,__VA_ARGS__) + +#define LIST_INIT(mylist, thestructptr) \ + mylist.firstel = &thestructptr; \ + mylist.lastel = &thestructptr; + +#define LIST_ADD(mylist, thestructptr) \ + mylist.lastel->next = thestructptr; \ + mylist.lastel = thestructptr; + +typedef struct file_record file_record; +struct file_record { + char *infilename; + char *outfilename; + int in_fd; + int out_fd; + int wd; + off_t in_pos; + off_t pos; + off_t last_uploaded_pos; + int recipe_id; + int panic; /* 0 : no info, 1 : paniced , -1 : watchdog=panic */ + int panic_taskid; + struct file_record *next; +}; +typedef struct file_record *file_record_ptr; + +typedef struct file_record_lst { + file_record_ptr firstel; + file_record_ptr lastel; +} file_record_list; + +/* function prototypes */ +size_t get_recipe_http_res(char *, size_t , size_t , void *); +int prepare_and_upload(file_record_ptr); +static void startelement(void *, const xmlChar *, const xmlChar **); +static int read_config_file(int , const char *); +static void get_task_id(file_record_ptr); +static void abort_recipe(int); +/* end of prototypes */ + +/* global vars */ +int DEBUG_LEVEL = 0; +file_record_list thelist; +char * url = NULL; +static const char base64table[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +int inotify_fd; +char * config_file; +regex_t regex; + +/* libcurl-related stuff */ +struct http_res_struct { + char *response; + int size; +}; +typedef struct http_res_struct http_res_struct; +typedef http_res_struct * http_res_struct_ptr; +/* end of libcurl-related vars */ + +///// libxml2 vars ////////// +enum states { + IN_RECIPE, /* inside the recipe we are interested in*/ + NOT_IN_RECIPE, /* not in the recipe we are searching for */ + FOUND +} parser_state; + +static xmlSAXHandler handler = { + NULL, /* internalSubset */ + NULL, /* isStandalone */ + NULL, /* hasInternalSubset */ + NULL, /* hasExternalSubset */ + NULL, /* resolveEntity */ + NULL, /* getEntity */ + NULL, /* entityDecl */ + NULL, /* notationDecl */ + NULL, /* attributeDecl */ + NULL, /* elementDecl */ + NULL, /* unparsedEntityDecl */ + NULL, /* setDocumentLocator */ + NULL, /* startDocument */ + NULL, /* endDocument */ + startelement, /* startElement */ + NULL, /* endElement */ + NULL, /* reference */ + NULL, /* characters */ + NULL, /* ignorableWhitespace */ + NULL, /* processingInstruction */ + NULL, /* comment */ + NULL, /* xmlParserWarning */ + NULL, /* xmlParserError */ + NULL, /* xmlParserError */ + NULL, /* getParameterEntity */ + NULL, /* cdataBlock; */ + NULL, /* externalSubset; */ + 1, + NULL, + NULL, /* startElementNs */ + NULL, /* endElementNs */ + NULL /* xmlStructuredErrorFunc */ +}; + +static xmlSAXHandlerPtr saxhandler = &handler; +//// end of libxml2 ////// + +size_t get_recipe_http_res(char *res_ptr, size_t size, size_t nb_byte, void *userdata) { + + int total_bytes; + total_bytes = size * nb_byte; + http_res_struct_ptr my_response = (http_res_struct_ptr) userdata; + + my_response->response = realloc(my_response->response, (my_response->size + total_bytes + 1)); + if ( my_response->response == NULL ) { + fprintf(stderr, "problem allocating memory for my_response->response"); + return -1; + } + memcpy(&(my_response->response[my_response->size]), res_ptr, total_bytes); + if ( &my_response->response[my_response->size] == NULL ) { + fprintf(stderr, "problem with copying to my_response->response"); + return -1; + } + my_response->size += total_bytes; + my_response->response[my_response->size] = '\0'; + + DEBUG4("total bytes :%d", total_bytes); + DEBUG4("total size: %d", my_response->size); + return total_bytes; +} + +//////////////// LIBXML2 //////////////////////////////////////////////// +/* startelement callback function for saxparser */ +static void +startelement(void *user_data, const xmlChar *name, const xmlChar **atts) +{ + int i=0, recipe_id_tmp; + int recipe_id; + file_record_ptr tmp_structptr; + + tmp_structptr = (file_record_ptr) user_data; + recipe_id = tmp_structptr->recipe_id; + DEBUG3(" callback for recipe %d ", recipe_id); + + if ( parser_state == FOUND || tmp_structptr->panic == -1 ) { + DEBUG3("%s" , "parser state is found or panic is set to -1"); + return; + } + if ( !strcmp((char *)name, "guestrecipe") ) { + if (atts != NULL ) { + for (i=0; (atts[i] != NULL); i++ ) { + if (!strcmp((char *)atts[i],"id")) { + i++; + recipe_id_tmp = atoi((const char *)atts[i]); + if ( recipe_id_tmp == recipe_id ) { + parser_state = IN_RECIPE; + } else { + parser_state = NOT_IN_RECIPE; + } + } + } + } + return; + } + /* <watchdog panic="ignore"/> */ + if ( !strcmp((char *)name, "watchdog") && parser_state == IN_RECIPE ) { + if ( atts != NULL ) { + for (i=0; (atts[i] != NULL); i++ ) { + if (!strcmp((char *)atts[i],"panic")) { + i++; + if(!strcmp((char *)atts[i],"ignore")) { + DEBUG3("panic ignored for recipe %d", recipe_id); + tmp_structptr->panic = -1; + } + } + } + } + return; + } + + if ( !strcmp((char *)name, "task" ) && parser_state == IN_RECIPE ) { + if ( atts != NULL ) { + for (i=0; (atts[i] != NULL); i++ ) { + if (!strcmp((char *)atts[i],"id")) { + i++; + tmp_structptr->panic_taskid = atoi((const char *)atts[i]); + tmp_structptr->panic = 1; + } + if(!strcmp((char *)atts[i],"status")) { + i++; + if(!strcmp((char *)atts[i],"Running")) { + parser_state = FOUND; + } + + } + } + } + } + return; +} +/* + * reads config file which has the original console log file name per line + */ +static int read_config_file(int inotify_fd, const char *configfile ) { + char *infilename = NULL; + char *outfilename = NULL; + int in_fd, out_fd, wd, errno; + long int recipe_id; + off_t pos_ret, in_pos_ret; + FILE *fp; + ssize_t line_read; + size_t line_len = 0; + int file_len; + char *line = NULL; + char *check_str = NULL; + char *ch = NULL; + char *recipe_str = NULL; + char *endptr; + const char *suffix = ".out"; + int i = 0; + file_record_ptr newrecord = NULL; + + if ( configfile == NULL ) { + fprintf(stderr, "Problem in read_config_file"); + return 1; + } + fp = fopen(configfile, "r"); + if (!fp) { + perror("problem opening configfile: "); + return 1; + } + + while ( (line_read = getline(&line, &line_len, fp) ) != -1 ) { + check_str = NULL; + i = 0; + /* ignore comments */ + if ( line[0] == '#' || isspace(line[0])) { + continue; + } + + ch = strchr(line, ' '); + if(!ch) { + fprintf(stderr, "Wrong format of config file. Filename Recipeid\n"); + fclose(fp); + return 1; + } + file_len = ch - line; + infilename = malloc( (size_t) ( file_len + 1 ) ); + memcpy(infilename, &line[0], file_len); + infilename[file_len] = '\0'; + + /* if we already watching this file, continue */ + file_record_ptr el; + for (el = thelist.firstel; el != NULL; el=el->next) { + if (strcmp(el->infilename,infilename) == 0 ) { + check_str = el->infilename; + break; + } + } + if ( check_str != NULL ) { + fprintf(stderr,"%s is already being watched\n",el->infilename); + continue; + } + + /* got the filename to watch , now get the recipeid */ + recipe_str=(char *)malloc((size_t) (line_read - file_len)); + if(!recipe_str) { + perror("Error mallocing recipe_str: "); + fclose(fp); + return 1; + } + while ( *ch != '\0' && *ch != '\n' ) { + if ( isspace(*ch) ) { + ch++; + continue; + } + recipe_str[i++] = *ch++; + } + recipe_str[i] = '\0'; + errno = 0; + recipe_id = strtol(recipe_str, &endptr, 10); + if(errno) { + perror("Problem converting recipeid to int"); + return 1; + } + if ( *endptr != '\0') { + fprintf(stderr, "%s is not a valid number\n", recipe_str); + return 1; + } + fprintf(stdout, "recipe_id is: %ld \n", recipe_id); + free(recipe_str); + + /* allocate space for the output file */ + outfilename = malloc( (size_t) file_len + 5 ); + if (!outfilename) { + perror("Error mallocing for outfilename: "); + return 1; + } + memcpy(outfilename, infilename, file_len); + strcpy(&outfilename[file_len], suffix); + fprintf(stdout, "outfilename is: %s \n", outfilename); + in_fd = open(infilename, O_RDONLY|O_CREAT, 0777); + if ( in_fd == -1 ) { + perror("open in_fd: "); + return 1; + } + out_fd = open(outfilename, O_WRONLY|O_CREAT|O_SYNC , 0664); + if ( out_fd == -1 ) { + perror("open out_fd: "); + return 1; + } + + in_pos_ret = lseek(in_fd, 0, SEEK_END); + if (in_pos_ret < 0 ) { + fprintf(stderr, "lseek in_fd\n"); + in_pos_ret = 0; + } + fprintf(stdout, "in_pos_ret is: %d\n", (int)in_pos_ret); + + pos_ret = lseek(out_fd, 0, SEEK_END); + fprintf(stdout, "pos_ret is: %d\n", (int)pos_ret); + if (pos_ret == (off_t) -1 ) { + fprintf(stderr, "Problem seeking outputfile\n"); + close(in_fd); + close(out_fd); + return 1; + } + /* initialize a struct */ + newrecord = (file_record_ptr) malloc(sizeof(file_record)); + if (!newrecord) { + perror("Error mallocing for newrecord: "); + return 1; + } + wd = inotify_add_watch (inotify_fd, infilename, IN_MODIFY|IN_CLOSE_WRITE|IN_CLOSE_NOWRITE); + if ( wd < 0 ) { + perror("Error in inotify_add_watch in __FUNC__ "); + return 1; + } + fprintf(stdout, "added file %s of recipe %ld for %s to descriptor %d \n", infilename, recipe_id, outfilename, inotify_fd); + fprintf(stdout, "in_fd %d , outfd: %d \n", in_fd, out_fd ); + fflush(stdout); + + newrecord->infilename = strdup(infilename); + newrecord->outfilename = outfilename; + newrecord->in_fd = in_fd; + newrecord->out_fd = out_fd; + newrecord->wd = wd; + newrecord->in_pos = in_pos_ret; + newrecord->pos = pos_ret; + newrecord->last_uploaded_pos = pos_ret; + newrecord->recipe_id = recipe_id; + newrecord->panic = 0; + newrecord->panic_taskid = -1; + newrecord->next = NULL; + + + if (thelist.firstel == NULL ) { + thelist.firstel = newrecord; + thelist.lastel = newrecord; + } else { + LIST_ADD(thelist, newrecord); + } + + check_str = NULL; + + } + fclose(fp); + return 0; +} + +static void submit_panic(int task_id) { + xmlrpc_env env; + xmlrpc_client *clientP; + xmlrpc_value *resultP; + + xmlrpc_env_init(&env); + if(env.fault_occurred) { + fprintf(stderr, "XML-RPC Fault in xmlrpc_env_init: %s (%d)\n", + env.fault_string, env.fault_code); + } + xmlrpc_client_setup_global_const(&env); + xmlrpc_client_create(&env, XMLRPC_CLIENT_NO_FLAGS, "xmlrpc client", "1.0", NULL, 0, &clientP); + if(env.fault_occurred) { + fprintf(stderr, "XML-RPC Fault in xmlrpc_client_create: %s (%d)\n", + env.fault_string, env.fault_code); + } + /* task_result(task()['id'], 'panic', '/', 0, panic.group()) */ + xmlrpc_client_call2f(&env, clientP, url, "task_result", &resultP, "(issis)", + (xmlrpc_int) task_id, "panic", "/", (xmlrpc_int)0, " "); + if(env.fault_occurred) { + fprintf(stderr, "XML-RPC Fault in xmlrpc_client_call2f: %s (%d)\n", + env.fault_string, env.fault_code); + } + xmlrpc_env_clean(&env); + xmlrpc_client_destroy(clientP); + xmlrpc_client_teardown_global_const(); + + return; + +} + +static void abort_recipe(int recipe_id) { + CURL *curl; + CURLcode res; + char recipe_str[32] = { '\0' }; + char * recipe_url_str; + int host_len; + /* url: http://%s:8000/RPC2 */ + /* /recipes/(recipe_id)/status */ + sprintf(&recipe_str[0], "%d" , recipe_id); + host_len = strlen(url) - 4 ; + recipe_url_str = (char *) malloc(host_len + 15 + strlen(recipe_str) + 1 ); + recipe_url_str = strncpy(recipe_url_str, url, host_len); + sprintf(&recipe_url_str[host_len], "recipes/%s/status", recipe_str); + recipe_url_str[host_len+15+strlen(recipe_str)] = '\0'; + + curl = curl_easy_init(); + if(curl) { + + curl_easy_setopt(curl, CURLOPT_URL, recipe_url_str); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "status=Aborted"); + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + /* Check for errors */ + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } else if ( res == 400 ) { + DEBUG0("status update for recipe %d failed ..", recipe_id); + } + /* always cleanup */ + curl_easy_cleanup(curl); + free(recipe_url_str); + } +} + +static void get_task_id(file_record_ptr therecord) { + CURL *curl; + CURLcode res; + xmlParserCtxtPtr ctxt; + http_res_struct response_struct; + char recipe_str[32] = { '\0' }; + char * recipe_url_str; + int host_len; + int recipe_id = therecord->recipe_id; + response_struct.response = (char *) malloc(1); + response_struct.size = 0; + parser_state = NOT_IN_RECIPE; + + sprintf(&recipe_str[0], "%d" , recipe_id); + host_len = strlen(url) - 4 ; + recipe_url_str = (char *) malloc(host_len + 8 + strlen(recipe_str) + 2 ); + recipe_url_str = strncpy(recipe_url_str, url, host_len); + sprintf(&recipe_url_str[host_len], "recipes/%s/", recipe_str); + recipe_url_str[host_len+8+strlen(recipe_str)+1] = '\0'; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, recipe_url_str); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, get_recipe_http_res); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_struct); + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + /* Check for errors */ + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + } + /* always cleanup */ + curl_easy_cleanup(curl); + free(recipe_url_str); + } + + /* parse xml */ + ctxt = xmlCreatePushParserCtxt(saxhandler, (void *)therecord, NULL, 0, NULL); + xmlParseChunk(ctxt, response_struct.response, (strlen(response_struct.response) + 1), 0); + xmlParseChunk(ctxt, response_struct.response, 0, 1); + xmlFreeParserCtxt(ctxt); + free(response_struct.response); + return; + +} + +int prepare_and_upload(file_record_ptr therecord) { + + /* file i/o */ + off_t start_pos, end_pos; + char *filename; + FILE *fp; + off_t rc; + char *read_buf; + size_t read_size; + size_t read_rc; + int i = 0; + /* md5 stuff */ + EVP_MD_CTX mdctx; + const EVP_MD *md; + unsigned char md_value[EVP_MAX_MD_SIZE]; + char md_hexdigest[EVP_MAX_MD_SIZE*2]; + unsigned int md_len; + int j; + /* xmlrpc */ + xmlrpc_env env; + xmlrpc_client *clientP; + xmlrpc_value *resultP; + /* base64 */ + int len = 0; + int inlen = 0; + int padit = 0; + int outlen = 0; + int outsize; + char *outstream; + + filename = therecord->outfilename; + start_pos = therecord->last_uploaded_pos; + end_pos = therecord->pos; + + OpenSSL_add_all_digests(); + + md = EVP_get_digestbyname("md5"); + if(!md) { + printf("Unknown message digest "); + exit(1); + } + + read_size = (size_t) end_pos - start_pos; + if ( read_size == 0 ) { + return 0; + } + //printf("readsize: %d startpos: %d endpos: %d \n", (int)read_size, (int)start_pos, (int)end_pos); + /* read_size + 1 for the terminating \0 for regexec() function */ + read_buf = (char *)malloc(read_size+1); + if (!read_buf) { + perror("Error mallocing read_buf: "); + return 1; + } + memset(read_buf, '\0', read_size+1); + fp = fopen(filename, "r"); + if(!fp) { + perror("Error opening file in __FUNC__ :"); + return 1; + } + + rc = fseek(fp, start_pos, SEEK_SET); + if ( rc == (off_t) -1 ) { + perror("Error in lseek: "); + fclose(fp); + return 1; + } + read_rc = fread( read_buf, 1, read_size, fp); + if (read_rc < 0 && (feof(fp) > 0 ) ) { + fprintf(stderr, "Problem with reading from %s, %d\n", filename, (int)read_rc); + fprintf(stderr, "read_buf is now: |%s| \n", read_buf); + fclose(fp); + free(read_buf); + return 1; + } + fclose(fp); + DEBUG5("Read buf: %s", read_buf); + + /* get rid of ^M s */ + for(i = 0; i < read_size; i++) { + if (read_buf[i] == 0x0d) { + read_buf[i] = 0x20; + } + } + + /* see if there is a panic string in the console */ + rc = regexec(®ex, read_buf, 0, NULL, 0); + if ( rc == 0 && therecord->panic == 0 ) { + DEBUG4("Found panic string on recipe %d ", therecord->recipe_id); + /* if match , submit panic result */ + get_task_id(therecord); + /* make sure that we get panic_task_id */ + if ( therecord->panic == 1 && therecord->panic_taskid != -1 ) { + DEBUG1("submitting panic for recipe %d taskid %d", therecord->recipe_id,therecord->panic_taskid); + submit_panic(therecord->panic_taskid); + } + + } + + + /* encode the read_buffer */ + outsize = (( read_size / 3 ) * 4 ); + if ( read_size%3 > 0 ) { + outsize += 4; + } + /* for terminating \0 */ + outsize++; + outstream = (char *) malloc((size_t)outsize); + if(!outstream) { + perror("Allocating for outstream:\n"); + free(read_buf); + return 1; + } + memset(outstream, '\0', outsize); + while ( len < read_size ) { + + for( i = 0; i < 3; i++,inlen++) { + if ( inlen+1 > read_size ) { + padit++; + } + + if ( i == 2 && (read_size > (len+2))) { + outstream[outlen++] = (unsigned char) base64table[ (int)(read_buf[inlen-2] >> 2) ]; + outstream[outlen++] = (unsigned char) base64table[ (int)(((read_buf[inlen-2] & 0x03) << 4) | ((read_buf[inlen-1] & 0xf0) >> 4)) ]; + outstream[outlen++] = (unsigned char) (padit >= 2 ? '=' : base64table[ (int)(((read_buf[inlen-1] & 0x0f) << 2) | ((read_buf[inlen] & 0xc0) >> 6)) ]); + outstream[outlen++] = (unsigned char) (padit >= 1 ? '=' : base64table[ (int)(read_buf[inlen] & 0x3f) ] ); + } else if ( i == 2 && (read_size == len+2) ) { + outstream[outlen++] = (unsigned char) base64table[ (int)(read_buf[inlen-2] >> 2) ]; + outstream[outlen++] = (unsigned char) base64table[ (int)(((read_buf[inlen-2] & 0x03) << 4) | ((read_buf[inlen-1] & 0xf0) >> 4)) ]; + outstream[outlen++] = (unsigned char) base64table[ (int)(((read_buf[inlen-1] & 0x0f) << 2))]; + outstream[outlen++] = '='; + } else if ( i == 2 && (read_size == len+1) ) { + outstream[outlen++] = (unsigned char) base64table[ (int)(read_buf[inlen-2] >> 2) ]; + outstream[outlen++] = (unsigned char) base64table[ (int)(((read_buf[inlen-2] & 0x03) << 4)) ]; + outstream[outlen++] = '='; + outstream[outlen++] = '='; + } + } + len += 3; + } + + outstream[outlen] = '\0'; + + /* md5 hashsum of the blob */ + EVP_MD_CTX_init(&mdctx); + EVP_DigestInit_ex(&mdctx, md, NULL); + EVP_DigestUpdate(&mdctx, read_buf, read_size); + EVP_DigestFinal_ex(&mdctx, md_value, &md_len); + EVP_MD_CTX_cleanup(&mdctx); + + for(i = 0, j = 0; i < md_len; i++){ + sprintf(&md_hexdigest[j], "%02x", md_value[i]); + j += 2; + } + md_hexdigest[j] = '\0'; + + + xmlrpc_env_init(&env); + if(env.fault_occurred) { + fprintf(stderr, "XML-RPC Fault in xmlrpc_env_init: %s (%d)\n", + env.fault_string, env.fault_code); + } + xmlrpc_client_setup_global_const(&env); + xmlrpc_client_create(&env, XMLRPC_CLIENT_NO_FLAGS, "xmlrpc client", "1.0", NULL, 0, &clientP); + if(env.fault_occurred) { + fprintf(stderr, "XML-RPC Fault in xmlrpc_client_create: %s (%d)\n", + env.fault_string, env.fault_code); + } + xmlrpc_client_call2f(&env, clientP, url, "recipe_upload_file", &resultP, "(issisis)", + (xmlrpc_int) therecord->recipe_id, "/", "console.log", (xmlrpc_int)read_size, + md_hexdigest,(xmlrpc_int)start_pos, outstream); + if(env.fault_occurred) { + fprintf(stderr, "XML-RPC Fault in xmlrpc_client_call2f: %s (%d)\n", + env.fault_string, env.fault_code); + } + xmlrpc_env_clean(&env); + xmlrpc_client_destroy(clientP); + xmlrpc_client_teardown_global_const(); + + therecord->last_uploaded_pos = (end_pos - 1); + /* if we got panic and no ignore, update the the recipe status */ + if ( therecord->panic == 1 ) { + DEBUG1("Aborting recipe: %d ", therecord->recipe_id); + abort_recipe(therecord->recipe_id); + } + free(outstream); + free(read_buf); + return 0; +} + +void sighandler (int sig) +{ + DEBUG0("calling sighandler for %d", sig); + + if ( sig == SIGTERM || sig == SIGINT ) { + DEBUG0("calling sighandler for %d", sig); + fflush(stdout); + fflush(stderr); + } else if ( sig == SIGUSR1 ) { + DEBUG0("got SIGUSR1, reloading the config file %s", config_file); + fflush(stdout); + fflush(stderr); + read_config_file(inotify_fd, config_file); + } +} + +int main(int argc, char *argv[], char *envp[]) { + int wr_ret; + int in_fd, out_fd; + ssize_t len = -1 , i = 0; + char events[EVENT_SIZE] = {0}; + ssize_t read_bytes; + char read_buf[BUF_SIZE]; + off_t in_pos, in_pos_end; + int rc; + int found = 0; + char *tmp_url = NULL; + char *pidfile = NULL; + char *baseprogname = NULL; + char *lc_arg = NULL; + FILE *PIDFP; + pid_t pid, sid, childpid; + thelist.firstel = NULL; + thelist.lastel = NULL; + struct sigaction reload_action; + + memset(&reload_action, 0, sizeof(reload_action)); + reload_action.sa_handler = sighandler; + + + + if ( argc < 3 ) { + fprintf(stderr, "Usage: %s [--config configfile] <--lc lab_controller>\n", argv[0]); + return 1; + } + + for(i = 0; i < argc; i++) { + if ( strncmp(argv[i],"--config",8) == 0 ) { + i++; + config_file=strdup(argv[i]); + } else if ( strncmp(argv[i],"--pidfile",9) == 0 ) { + i++; + pidfile=strdup(argv[i]); + } else if ( strncmp(argv[i],"--lc", 4) == 0 ) { + i++; + lc_arg=strdup(argv[i]); + } else if ( strncmp(argv[i],"--v",3) == 0 ) { + int debug_len = strlen(argv[i]) - 2; + char * tmp_str = (char *) malloc(debug_len+1); + + memset(tmp_str, 'v', debug_len); + tmp_str[debug_len] = '\0'; + if ( strncmp(tmp_str, &argv[i][2], debug_len) == 0 ) { + DEBUG_LEVEL = debug_len; + printf("Debug level : %d \n", DEBUG_LEVEL); + } + } + } + if (!config_file) { + fprintf(stderr, "Usage: %s [--config configfile] <--pidfile pidfile>\n", argv[0]); + return 1; + } + if ( !pidfile ) { + baseprogname = basename(argv[0]); + i = strlen(baseprogname); + pidfile = (char *)malloc(i+14); + strncpy(pidfile,"/var/run/", 9); + strncpy(&pidfile[9],baseprogname,i); + strcpy(&pidfile[9+i],".pid"); + } + + sigaction(SIGTERM, &reload_action, NULL); + sigaction(SIGINT, &reload_action, NULL); + sigaction(SIGUSR1, &reload_action, NULL); + /* daemonize */ + pid = fork(); + if ( pid < 0 ) { + perror("Error with forking\n"); + return 1; + } else if ( pid > 0 ) { + return 0; + } + + sid = setsid(); + if ( sid < 0 ) { + perror("problem with setsid(): \n"); + return 1; + } + + if ( chdir("/") != 0 ) { + perror("problem with chdir(): \n"); + return 1; + } + childpid = getpid(); + fprintf(stdout,"childpid : %d \n", childpid); + PIDFP = fopen(pidfile, "w" ); + if (!PIDFP) { + fprintf(stderr, "Can't open pidfile"); + return 1; + } + rc = fprintf(PIDFP, "%d\n", childpid); + if ( rc < 0 ) { + fprintf(stderr, "Problem with writing the pidfile"); + return 1; + } + fflush(PIDFP); + + close(STDIN_FILENO); + freopen(STDOUT_REDIRECT, "a", stdout); + if(!stdout) { + perror("Error redirecting stdout: \n"); + return 1; + } + freopen(STDERR_REDIRECT, "a", stderr); + if(!stderr) { + perror("Error redirecting stderr: \n"); + return 1; + } + + /* if we get lan controller given on the command line, use it , or else see if + * there is LAB_CONTROLLER env. variable + */ + if ( lc_arg ) { + url = lc_arg; + } else { + url = getenv("LAB_CONTROLLER"); + } + + if ( url == NULL ) { + fprintf(stderr, "No Lab controller given on command line or env. vars\n"); + return 1; + } + /* http://$LAB_CONTROLLER:8000/RPC2 */ + tmp_url = (char *) malloc(strlen(url) + 18); + snprintf(tmp_url, (size_t) (strlen(url) + 18), "http://%s:8000/RPC2", url); + url = tmp_url; + + fprintf(stdout, "LAB CONTROLLER is : %s \n", url); + + /* compile regex */ + rc = regcomp(®ex, PANIC_REGEX, REG_EXTENDED); + if ( rc ) { + perror("Problem with compiling regex: "); + return 1; + } + + inotify_fd = inotify_init(); + if ( inotify_fd < 0 ) { + perror("Error inotify_init: "); + return 1; + } + rc = read_config_file(inotify_fd, config_file); + if(rc) { + fprintf(stderr, "Error reading config file\n"); + return 1; + } + while (1) { + len = read (inotify_fd, events, EVENT_SIZE); + for (i=0; i < len; i++) { + struct inotify_event *event = (struct inotify_event *)&events[i]; + /* find the wd in the list */ + file_record_ptr el = NULL; + found = 0; + for (el = thelist.firstel; el != NULL; el=el->next) { + if (el->wd == event->wd ) { + in_fd = el->in_fd; + out_fd = el->out_fd; + found = 1; + DEBUG3("event for recipe: %d", el->recipe_id); + break; + } + } + + if ( found == 0 ) { + continue; + } + + if (event->mask & IN_MODIFY) { + in_pos = lseek(in_fd, 0, SEEK_CUR); + if (in_pos < 0) { + fprintf(stderr, "IN_MODIFY lseek err\n"); + } else { + in_pos_end = lseek(in_fd, 0, SEEK_END); + if (in_pos_end < 0) { + fprintf(stderr, "lseek SEEK_END err\n"); + } else { + /* in file was truncated, seek to beginning */ + if (in_pos_end < el->in_pos) + in_pos = 0; + } + lseek(in_fd, in_pos, SEEK_SET); + } + + while( (read_bytes = read(in_fd, read_buf, BUF_SIZE) ) > 0 ) { + wr_ret = write(out_fd,read_buf,read_bytes); + DEBUG3("written in_fd: %d", in_fd); + DEBUG3("written %d bytes:", wr_ret); + if ( wr_ret == -1 ) { + perror("Error writing out: "); + return 1; + } + el->pos = lseek(out_fd, 0L, SEEK_CUR); + if ( el->pos == (off_t) -1 ) { + fprintf(stderr,"Problem reading pos from outfd\n"); + return 1; + } + if ((off_t) (el->pos - el->last_uploaded_pos) > 1024) { + prepare_and_upload(el); + } + } + in_pos_end = lseek(in_fd, 0, SEEK_CUR); + if (in_pos_end < 0) { + fprintf(stderr, "lseek in_fd after write err\n"); + } else { + el->in_pos = in_pos_end; + } + } else if ((event->mask & IN_CLOSE_WRITE) || (event->mask & IN_CLOSE_NOWRITE) ) { + prepare_and_upload(el); + } + + } + bzero(&events[0],EVENT_SIZE); + } + return 0; +} + diff --git a/distribution/virt/import/logguestconsoles.initd b/distribution/virt/import/logguestconsoles.initd new file mode 100755 index 0000000..8698f6d --- /dev/null +++ b/distribution/virt/import/logguestconsoles.initd @@ -0,0 +1,109 @@ +#!/bin/sh +# +# logguestconsoles Agent for reporting virtual guest IDs to subscription-manager +# +# chkconfig: 2345 95 99 +# description: Agent for reporting virtual guest IDs to subscription-manager + +### BEGIN INIT INFO +# Provides: logguestconsoles +# Required-Start: $network libvirtd +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop logguestconsoles +# Description: Agent for reporting virtual guest IDs to subscription-manager +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +exec="/usr/local/bin/logguestconsoles" +prog="logguestconsoles" +config="/usr/local/etc/logguestconsoles.conf" +pidfile="/var/run/$prog.pid" +args="--config $config" + +# Export all variables in /etc/sysconfig/logguestconsoles +set -a +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog +# below is where we'll get the LAB_CONTROLLER +[ -e /etc/profile.d/rh-env.sh ] && . /etc/profile.d/rh-env.sh +set +a + +lockfile=/var/lock/subsys/logguestconsoles + +start() { + [ -x $exec ] || exit 5 + echo -n $"Starting $prog: " + daemon --pidfile $pidfile $exec $args + retval=$? + echo + [ $retval -eq 0 ] && touch $lockfile + return $retval +} + +stop() { + echo -n $"Stopping $prog: " + killproc -p $pidfile $prog + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +restart() { + stop + start +} + +reload() { + kill -s USR1 $(cat $pidfile) +} + +force_reload() { + restart +} + +rh_status() { + status -p $pidfile $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? diff --git a/distribution/virt/import/lxml_guestname_resolution.py b/distribution/virt/import/lxml_guestname_resolution.py new file mode 100755 index 0000000..551a238 --- /dev/null +++ b/distribution/virt/import/lxml_guestname_resolution.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# +# this script is used to update the /etc/hosts file in the host/dom0 so that the +# guestname will be resolved to the guest's IP address. +# + +import sys +import os +import time +import xmlrpclib +from lxml import etree +from cStringIO import StringIO +from optparse import OptionParser +import socket + +#start here +usage = "usage: %prog --recipeid " +parser = OptionParser() +parser.add_option("-r", "--recipeid", dest="recipeid", help="recipe id of the guest to hostname of.") +(options, args) = parser.parse_args() +recipeid = options.recipeid + +xml_tries = 1 +interval = 300 +proxy = xmlrpclib.ServerProxy('http://%s:8000/RPC2' % os.environ['LAB_CONTROLLER']) +while xml_tries < 5: + try: + recipe_xml = proxy.get_my_recipe(dict(recipe_id=recipeid)) + break + except: + print "Couldn't get guestinfo from %s . sleeping %i secs" % (os.environ['LAB_CONTROLLER'] , interval) + time.sleep(interval) + xml_tries += 1 + +if xml_tries == 5: + print "Can't get guestinfo from %s" % os.environ['LAB_CONTROLLER'] + sys.exit(1) + +thetree = etree.parse(StringIO(recipe_xml)) +for node in thetree.xpath('/job/recipeSet/recipe/guestrecipe'): + print "in node" + if node.attrib['id'] == recipeid: + guestname = node.attrib['guestname'] + guesthost = node.attrib['system'] + guestip = socket.gethostbyname(guesthost) + print "guestname: " + guestname + " guesthost: " + guesthost + " ip: " + guestip + # update /etc/hosts with this info + fh = open('/etc/hosts', 'a') + fh.write(guestip+" "+guestname+"\n") + fh.close() + +sys.exit(0) + + diff --git a/distribution/virt/import/minidom_guestname_resolution.py b/distribution/virt/import/minidom_guestname_resolution.py new file mode 100755 index 0000000..ec88246 --- /dev/null +++ b/distribution/virt/import/minidom_guestname_resolution.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# +# this script is used to update the /etc/hosts file in the host/dom0 so that the +# guestname will be resolved to the guest's IP address. +# + +import sys +import os +import time +import xmlrpclib +import xml.dom.minidom +from cStringIO import StringIO +from optparse import OptionParser +import socket + +#start here +usage = "usage: %prog --recipeid " +parser = OptionParser() +parser.add_option("-r", "--recipeid", dest="recipeid", help="recipe id of the guest to hostname of.") +(options, args) = parser.parse_args() +recipeid = options.recipeid + +xml_tries = 1 +interval = 300 +proxy = xmlrpclib.ServerProxy('http://%s:8000/RPC2' % os.environ['LAB_CONTROLLER']) +while xml_tries < 5: + try: + recipe_xml = proxy.get_my_recipe(dict(recipe_id=recipeid)) + break + except: + print "Couldn't get guestinfo from %s . sleeping %i secs" % (os.environ['LAB_CONTROLLER'] , interval) + time.sleep(interval) + xml_tries += 1 + +if xml_tries == 5: + print "Can't get guestinfo from %s" % os.environ['LAB_CONTROLLER'] + sys.exit(1) + +dom = xml.dom.minidom.parseString(recipe_xml) +for el in dom.getElementsByTagName('guestrecipe'): + mydict = dict(el.attributes.items()) + for key in mydict: + if key == 'id' and mydict[key] == recipeid: + guestname = mydict['guestname'] + guesthost = mydict['system'] + guestip = socket.gethostbyname(guesthost) + print "guestname: " + guestname + " guesthost: " + guesthost + " ip: " + guestip + # update /etc/hosts with this info + fh = open('/etc/hosts', 'a') + fh.write(guestip+" "+guestname+"\n") + fh.close() +sys.exit(0) + + diff --git a/distribution/virt/import/ptytofile.sh b/distribution/virt/import/ptytofile.sh new file mode 100755 index 0000000..68f3b78 --- /dev/null +++ b/distribution/virt/import/ptytofile.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +if [[ $# != 1 ]]; then + echo "Usage: $0 [guestname] " + exit 1 +fi + +# if the guest isn't shutdown, shut it down.. try at least 2 times +guestup=0 +i=0 +while (( $i < 2 )) ; +do + if virsh list | grep -w $1; then + guestup=1 + if ! virsh shutdown $1 ; then + echo "problem with virsh shutdown $1" + exit 1 + fi + sleep 90 + fi + let "i=${i}+1" +done + +if virsh list | grep -w $1; then + echo "the guest can't be brought down" + exit 1 +fi + +if ! virsh dumpxml $1 > $1.xml ; then + echo "problem with virsh dumpxml $1" + exit 1 +fi + +# workaround for BZ 731115 +l_guest_name=$(echo $1 | tr [:upper:] [:lower:]) +REPL_STR=$(cat << EOF + <serial type='file'>\n + <source path='/mnt/tests/distribution/virt/install/${l_guest_name}/logs/${l_guest_name}_console.log'/>\n + <target port='0'/>\n + </serial>\n + <console type='file'>\n + <source path='/mnt/tests/distribution/virt/install/${l_guest_name}/logs/${l_guest_name}_console.log'/>\n + <target port='0'/>\n + </console> +EOF +) + +serialline=$(grep -n '<serial ' $1.xml | awk -F: '{print $1}') +let "serialline=${serialline}-1" +consoleline=$(grep -n '</console>' $1.xml | awk -F: '{print $1}') +let "consoleline=${consoleline}+1" +sed -n '1,'"${serialline}"'p' $1.xml > $1.xml.tmp +echo -e $REPL_STR >> $1.xml.tmp +sed -n ''"${consoleline}"',$p' $1.xml >> $1.xml.tmp + +#redefine the guest with the edited xml +if ! virsh define ./$1.xml.tmp; then + echo "problem with virsh define ./$1.xml.tmp" + exit 1 +fi + +if [[ ${guestup} == 1 ]]; then + if ! virsh start $1; then + echo "problem restarting guest $1 " + exit 1 + fi + # give it some time to start up. + sleep 90 + + if ! virsh list | grep -w $1 ; then + echo "guest doesn't seem to be up after restart" + exit 1 + fi +fi diff --git a/distribution/virt/import/rhel5_write_consolelogs.initd b/distribution/virt/import/rhel5_write_consolelogs.initd new file mode 100755 index 0000000..2365e80 --- /dev/null +++ b/distribution/virt/import/rhel5_write_consolelogs.initd @@ -0,0 +1,111 @@ +#!/bin/sh +# +# logguestconsoles Agent for reporting virtual guest IDs to subscription-manager +# +# chkconfig: 2345 95 95 +# description: Agent for reporting virtual guest IDs to subscription-manager + +### BEGIN INIT INFO +# Provides: logguestconsoles +# Required-Start: $network libvirtd +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop logguestconsoles +# Description: Agent for reporting virtual guest IDs to subscription-manager +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +exec="/usr/local/bin/rhel5_write_consolelogs" +prog="rhel5_write_consolelogs" +#config="/usr/local/etc/logguestconsoles.conf" +pidfile="/var/run/$prog.pid" +args="" + +# Export all variables in /etc/sysconfig/logguestconsoles +set -a +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog +# below is where we'll get the LAB_CONTROLLER +[ -e /etc/profile.d/rh-env.sh ] && . /etc/profile.d/rh-env.sh +set +a + +lockfile=/var/lock/subsys/rhel5_write_consolelogs + +start() { + [ -x $exec ] || exit 5 + echo -n $"Starting $prog: " + #daemon --pidfile $pidfile nohup $exec & + nohup $exec & + childpid=$! + if kill -0 ${childpid}; then + echo + touch $lockfile + echo "${childpid}" > ${pidfile} + return 0 + else + return -1 + fi +} + +stop() { + echo -n $"Stopping $prog: " + killproc -p $pidfile $prog + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +restart() { + stop + start +} + +force_reload() { + restart +} + +rh_status() { + status -p $pidfile $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? diff --git a/distribution/virt/import/rhel5_write_consolelogs.py b/distribution/virt/import/rhel5_write_consolelogs.py new file mode 100755 index 0000000..b34cc6e --- /dev/null +++ b/distribution/virt/import/rhel5_write_consolelogs.py @@ -0,0 +1,532 @@ +#!/usr/bin/python -u +# +# +# +################################################################################# +# Start off by implementing a general purpose event loop for anyones use +################################################################################# + +import sys +import getopt +import os +import libvirt +import select +import errno +import time +import threading +import subprocess +import signal +import pty +from xml.dom import minidom +from optparse import OptionParser + +debugstr = 0 + +# +# This general purpose event loop will support waiting for file handle +# I/O and errors events, as well as scheduling repeatable timers with +# a fixed interval. +# +# It is a pure python implementation based around the poll() API +# +class virEventLoopPure: + # This class contains the data we need to track for a + # single file handle + class virEventLoopPureHandle: + def __init__(self, handle, fd, events, cb, opaque): + self.handle = handle + self.fd = fd + self.events = events + self.cb = cb + self.opaque = opaque + + def get_id(self): + return self.handle + + def get_fd(self): + return self.fd + + def get_events(self): + return self.events + + def set_events(self, events): + self.events = events + + def dispatch(self, events): + self.cb(self.handle, + self.fd, + events, + self.opaque[0], + self.opaque[1]) + + # This class contains the data we need to track for a + # single periodic timer + class virEventLoopPureTimer: + def __init__(self, timer, interval, cb, opaque): + self.timer = timer + self.interval = interval + self.cb = cb + self.opaque = opaque + self.lastfired = 0 + + def get_id(self): + return self.timer + + def get_interval(self): + return self.interval + + def set_interval(self, interval): + self.interval = interval + + def get_last_fired(self): + return self.lastfired + + def set_last_fired(self, now): + self.lastfired = now + + def dispatch(self): + self.cb(self.timer, + self.opaque[0], + self.opaque[1]) + + + def __init__(self, debug=False): + self.debugOn = debug + self.poll = select.poll() + self.pipetrick = os.pipe() + self.nextHandleID = 1 + self.nextTimerID = 1 + self.handles = [] + self.timers = [] + self.quit = False + + # The event loop can be used from multiple threads at once. + # Specifically while the main thread is sleeping in poll() + # waiting for events to occur, another thread may come along + # and add/update/remove a file handle, or timer. When this + # happens we need to interrupt the poll() sleep in the other + # thread, so that it'll see the file handle / timer changes. + # + # Using OS level signals for this is very unreliable and + # hard to implement correctly. Thus we use the real classic + # "self pipe" trick. A anonymous pipe, with one end registered + # with the event loop for input events. When we need to force + # the main thread out of a poll() sleep, we simple write a + # single byte of data to the other end of the pipe. + self.debug("Self pipe watch %d write %d" %(self.pipetrick[0], self.pipetrick[1])) + self.poll.register(self.pipetrick[0], select.POLLIN) + + def debug(self, msg): + if self.debugOn: + print msg + + + # Calculate when the next timeout is due to occurr, returning + # the absolute timestamp for the next timeout, or 0 if there is + # no timeout due + def next_timeout(self): + next = 0 + for t in self.timers: + last = t.get_last_fired() + interval = t.get_interval() + if interval < 0: + continue + if next == 0 or (last + interval) < next: + next = last + interval + + return next + + # Lookup a virEventLoopPureHandle object based on file descriptor + def get_handle_by_fd(self, fd): + for h in self.handles: + if h.get_fd() == fd: + return h + return None + + # Lookup a virEventLoopPureHandle object based on its event loop ID + def get_handle_by_id(self, handleID): + for h in self.handles: + if h.get_id() == handleID: + return h + return None + + + # This is the heart of the event loop, performing one single + # iteration. It asks when the next timeout is due, and then + # calcuates the maximum amount of time it is able to sleep + # for in poll() pending file handle events. + # + # It then goes into the poll() sleep. + # + # When poll() returns, there will zero or more file handle + # events which need to be dispatched to registered callbacks + # It may also be time to fire some periodic timers. + # + # Due to the coarse granularity of schedular timeslices, if + # we ask for a sleep of 500ms in order to satisfy a timer, we + # may return upto 1 schedular timeslice early. So even though + # our sleep timeout was reached, the registered timer may not + # technically be at its expiry point. This leads to us going + # back around the loop with a crazy 5ms sleep. So when checking + # if timeouts are due, we allow a margin of 20ms, to avoid + # these pointless repeated tiny sleeps. + def run_once(self): + sleep = -1 + next = self.next_timeout() + self.debug("Next timeout due at %d" % next) + if next > 0: + now = int(time.time() * 1000) + if now >= next: + sleep = 0 + else: + sleep = (next - now) / 1000.0 + + self.debug("Poll with a sleep of %d" % sleep) + events = self.poll.poll(sleep) + + # Dispatch any file handle events that occurred + for (fd, revents) in events: + # See if the events was from the self-pipe + # telling us to wakup. if so, then discard + # the data just continue + if fd == self.pipetrick[0]: + data = os.read(fd, 1) + continue + + h = self.get_handle_by_fd(fd) + if h: + self.debug("Dispatch fd %d handle %d events %d" % (fd, h.get_id(), revents)) + h.dispatch(self.events_from_poll(revents)) + + now = int(time.time() * 1000) + for t in self.timers: + interval = t.get_interval() + if interval < 0: + continue + + want = t.get_last_fired() + interval + # Deduct 20ms, since schedular timeslice + # means we could be ever so slightly early + if now >= (want-20): + self.debug("Dispatch timer %d now %s want %s" % (t.get_id(), str(now), str(want))) + t.set_last_fired(now) + t.dispatch() + + + # Actually the event loop forever + def run_loop(self): + self.quit = False + while not self.quit: + self.run_once() + + def interrupt(self): + os.write(self.pipetrick[1], 'c') + + + # Registers a new file handle 'fd', monitoring for 'events' (libvirt + # event constants), firing the callback cb() when an event occurs. + # Returns a unique integer identier for this handle, that should be + # used to later update/remove it + def add_handle(self, fd, events, cb, opaque): + handleID = self.nextHandleID + 1 + self.nextHandleID = self.nextHandleID + 1 + + h = self.virEventLoopPureHandle(handleID, fd, events, cb, opaque) + self.handles.append(h) + + self.poll.register(fd, self.events_to_poll(events)) + self.interrupt() + + self.debug("Add handle %d fd %d events %d" % (handleID, fd, events)) + + return handleID + + # Registers a new timer with periodic expiry at 'interval' ms, + # firing cb() each time the timer expires. If 'interval' is -1, + # then the timer is registered, but not enabled + # Returns a unique integer identier for this handle, that should be + # used to later update/remove it + def add_timer(self, interval, cb, opaque): + timerID = self.nextTimerID + 1 + self.nextTimerID = self.nextTimerID + 1 + + h = self.virEventLoopPureTimer(timerID, interval, cb, opaque) + self.timers.append(h) + self.interrupt() + + self.debug("Add timer %d interval %d" % (timerID, interval)) + + return timerID + + # Change the set of events to be monitored on the file handle + def update_handle(self, handleID, events): + h = self.get_handle_by_id(handleID) + if h: + h.set_events(events) + self.poll.unregister(h.get_fd()) + self.poll.register(h.get_fd(), self.events_to_poll(events)) + self.interrupt() + + self.debug("Update handle %d fd %d events %d" % (handleID, h.get_fd(), events)) + + # Change the periodic frequency of the timer + def update_timer(self, timerID, interval): + for h in self.timers: + if h.get_id() == timerID: + h.set_interval(interval); + self.interrupt() + + self.debug("Update timer %d interval %d" % (timerID, interval)) + break + + # Stop monitoring for events on the file handle + def remove_handle(self, handleID): + handles = [] + for h in self.handles: + if h.get_id() == handleID: + self.poll.unregister(h.get_fd()) + self.debug("Remove handle %d fd %d" % (handleID, h.get_fd())) + else: + handles.append(h) + self.handles = handles + self.interrupt() + + # Stop firing the periodic timer + def remove_timer(self, timerID): + timers = [] + for h in self.timers: + if h.get_id() != timerID: + timers.append(h) + self.debug("Remove timer %d" % timerID) + self.timers = timers + self.interrupt() + + # Convert from libvirt event constants, to poll() events constants + def events_to_poll(self, events): + ret = 0 + if events & libvirt.VIR_EVENT_HANDLE_READABLE: + ret |= select.POLLIN + if events & libvirt.VIR_EVENT_HANDLE_WRITABLE: + ret |= select.POLLOUT + if events & libvirt.VIR_EVENT_HANDLE_ERROR: + ret |= select.POLLERR; + if events & libvirt.VIR_EVENT_HANDLE_HANGUP: + ret |= select.POLLHUP; + return ret + + # Convert from poll() event constants, to libvirt events constants + def events_from_poll(self, events): + ret = 0; + if events & select.POLLIN: + ret |= libvirt.VIR_EVENT_HANDLE_READABLE; + if events & select.POLLOUT: + ret |= libvirt.VIR_EVENT_HANDLE_WRITABLE; + if events & select.POLLNVAL: + ret |= libvirt.VIR_EVENT_HANDLE_ERROR; + if events & select.POLLERR: + ret |= libvirt.VIR_EVENT_HANDLE_ERROR; + if events & select.POLLHUP: + ret |= libvirt.VIR_EVENT_HANDLE_HANGUP; + return ret; + + +########################################################################### +# Now glue an instance of the general event loop into libvirt's event loop +########################################################################### + +# This single global instance of the event loop wil be used for +# monitoring libvirt events +eventLoop = virEventLoopPure(debug=False) + +# This keeps track of what thread is running the event loop, +# (if it is run in a background thread) +eventLoopThread = None + + +# These next set of 6 methods are the glue between the official +# libvirt events API, and our particular impl of the event loop +# +# There is no reason why the 'virEventLoopPure' has to be used. +# An application could easily may these 6 glue methods hook into +# another event loop such as GLib's, or something like the python +# Twisted event framework. + +def virEventAddHandleImpl(fd, events, cb, opaque): + global eventLoop + return eventLoop.add_handle(fd, events, cb, opaque) + +def virEventUpdateHandleImpl(handleID, events): + global eventLoop + return eventLoop.update_handle(handleID, events) + +def virEventRemoveHandleImpl(handleID): + global eventLoop + return eventLoop.remove_handle(handleID) + +def virEventAddTimerImpl(interval, cb, opaque): + global eventLoop + return eventLoop.add_timer(interval, cb, opaque) + +def virEventUpdateTimerImpl(timerID, interval): + global eventLoop + return eventLoop.update_timer(timerID, interval) + +def virEventRemoveTimerImpl(timerID): + global eventLoop + return eventLoop.remove_timer(timerID) + +# This tells libvirt what event loop implementation it +# should use +def virEventLoopPureRegister(): + libvirt.virEventRegisterImpl(virEventAddHandleImpl, + virEventUpdateHandleImpl, + virEventRemoveHandleImpl, + virEventAddTimerImpl, + virEventUpdateTimerImpl, + virEventRemoveTimerImpl) + +# Directly run the event loop in the current thread +def virEventLoopPureRun(): + global eventLoop + eventLoop.run_loop() + +# Spawn a background thread to run the event loop +def virEventLoopPureStart(): + global eventLoopThread + virEventLoopPureRegister() + eventLoopThread = threading.Thread(target=virEventLoopPureRun, name="libvirtEventLoop") + eventLoopThread.setDaemon(True) + eventLoopThread.start() + + +########################################################################## +# Everything that now follows is a simple demo of domain lifecycle events +########################################################################## +def eventToString(event): + eventStrings = ( "Defined", + "Undefined", + "Started", + "Suspended", + "Resumed", + "Stopped" ); + return eventStrings[event]; + +def detailToString(event, detail): + eventStrings = ( + ( "Added", "Updated" ), + ( "Removed" ), + ( "Booted", "Migrated", "Restored", "Snapshot" ), + ( "Paused", "Migrated", "IOError", "Watchdog" ), + ( "Unpaused", "Migrated"), + ( "Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot") + ) + return eventStrings[event][detail] + +def readconsoleandsave (domainname): + global testdir + filename = testdir+"/guests/"+domainname+"/logs/"+domainname+"_console.log" + args = ["/usr/bin/virsh"] + args = args + [ "console", "%s" % domainname] + console_fd = open(filename, "a+") + console_fid = console_fd.fileno() + fds[domainname] = console_fd + (child, fd) = pty.fork() + if not child: + os.dup2(console_fid, 1) + #time.sleep(1) + os.execvp(args[0], args) + os._exit(1) + else: + debugprint("Forked %s %s" % (args[0], args)) + # os.waitpid(-1, os.WNOHANG) + + #print "child is : %d " % child + return child + +def myDomainEventCallback1 (conn, dom, event, detail, opaque): + #print "name: %s event: %s " % ( dom.name(), eventToString(event) ) + global pids + if eventToString(event) == "Started": + debugprint("%s started, will call readconsoleandsave" % dom.name()) + pids[dom.name()] = readconsoleandsave(dom.name()) + #print "pids[%s] is %s " % (dom.name(), pids[dom.name()]) + elif eventToString(event) == "Stopped": + if dom.name() in fds: + fds[dom.name()].close() + if dom.name() in pids: + os.kill(pids[dom.name()],signal.SIGKILL) + +def usage(): + print "usage: "+os.path.basename(sys.argv[0])+" [uri]" + print " uri will default to qemu:///system" + +def debugprint(str): + global debugstr + if (debugstr): + print "%s" % str + + +def main(): + global testdir + #try: + # opts, args = getopt.getopt(sys.argv[1:], "h", ["help"] ) + #except getopt.GetoptError, err: + # # print help information and exit: + # print str(err) # will print something like "option -a not recognized" + # usage() + # sys.exit(2) + parser = OptionParser(conflict_handler="resolve") + parser.add_option("-t" ,"--testdir", dest="testdir", help="Root dir for logs", + default="/mnt/tests/distribution/virt/install", metavar="PATH") + parser.add_option("-h" ,"--help", dest="help", help="Display help", metavar="HELP") + parser.add_option("-d" ,"--debug", dest="debug", help="Print debug", + action="store_false", default=False, metavar="DEBUG") + (options, args) = parser.parse_args() + + if options.help: + usage() + sys.exit(0) + + if options.debug: + debugstr = 1 + + if options.testdir: + testdir = options.testdir + + if len(args) > 0: + uri = args[0] + else: + #uri = "qemu:///system" + uri = subprocess.Popen(["virsh", "uri"], stdout=subprocess.PIPE).communicate()[0].strip() + + print "Using uri: " + uri + + # Run a background thread with the event loop + virEventLoopPureStart() + + vc = libvirt.open(uri) + + # Close connection on exit (to test cleanup paths) + old_exitfunc = getattr(sys, 'exitfunc', None) + def exit(): + print "Closing " + str(vc) + vc.close() + if (old_exitfunc): old_exitfunc() + sys.exitfunc = exit + + #Add 2 callbacks to prove this works with more than just one + vc.domainEventRegister(myDomainEventCallback1,None) + + # The rest of your app would go here normally, but for sake + # of demo we'll just go to sleep. The other option is to + # run the event loop in your main thread if your app is + # totally event based. + while 1: + time.sleep(1) + + +if __name__ == "__main__": + pids = { } + fds = { } + path = "" + main() diff --git a/distribution/virt/import/rhts_submit_virt_logs b/distribution/virt/import/rhts_submit_virt_logs new file mode 100755 index 0000000..7aa567c --- /dev/null +++ b/distribution/virt/import/rhts_submit_virt_logs @@ -0,0 +1,58 @@ +#!/bin/bash +# +# This script is a wrap-around rhts_submit_log script to submit virtualization +# related logs. It submits logfiles based on the type of the hypervisor. +# + +if isxen; then + #submit xen logs + for xenlog in $(find /var/log/xen/ -type f | tr '\n' "${IFS}") + do + rhts_submit_log -l ${xenlog} + done + for xenlog in $(find /var/log/xen/console -type f | tr '\n' "${IFS}") + do + rhts_submit_log -l ${xenlog} + done + # submit dmesg + dmesg > ./dmesg.txt + rhts_submit_log -l ./dmesg.txt + +else + for qemulog in $(find /var/log/libvirt/qemu -type f | tr '\n' "${IFS}") + do + rhts_submit_log -l ${qemulog} + done + # submit dmesg + dmesg > ./dmesg.txt + rhts_submit_log -l ./dmesg.txt + +fi + +#submit libvirtd debug log... +if [ -e /tmp/libvirtd_debug.log ]; then +rhts_submit_log -l /tmp/libvirtd_debug.log +# clean the log for the next test +echo "" > /tmp/libvirtd_debug.log +fi + +if setvirttestargs; then + + if [[ x"${VIRT_TESTARGS}" != "x" ]]; then + OLDIFS=${IFS} + IFS="|" + for guestname in $VIRT_TESTARGS + do + if [ -d $(pwd)/${guestname}/logs ]; then + unset IFS + for file in $(ls $(pwd)/${guestname}/logs) + do + rhts_submit_log -l $(pwd)/${guestname}/logs/${file} + done + IFS="|" + fi + done + IFS=${OLDIFS} + fi +fi + diff --git a/distribution/virt/import/rhts_virt_funcs.sh b/distribution/virt/import/rhts_virt_funcs.sh new file mode 100755 index 0000000..bfbed1f --- /dev/null +++ b/distribution/virt/import/rhts_virt_funcs.sh @@ -0,0 +1,166 @@ +# This file will contain the general functions that might be useful to various +# virtualization testings. The file will sit in /usr/local/bin/ directory and will be +# intended to be sourced in by the tests utilizing this. + +# function isxen: +# return 0 if we're running in a xen hypervisor , return 1 if not +function isxen { + if uname -r | grep -q xen; then + return 0 + else + return 1 + fi +} + +# function iskvm: +# return 0 if we're running in a kvm hypervisor , return 1 if not +function iskvm { + if lsmod | grep -q kvm; then + return 0 + else + return 1 + fi +} + +# function isparavirt: +# return 0 if the $guest is paravirt +function isparavirt { + + if [[ $# != 1 ]]; then + echo "Need a guest name as an argument" + return 1; + fi + + if virsh dumpxml $1 | grep -q '>hvm<'; then + return 1 + else + return 0 + fi + +} + +# function ishvm +# return 0 if $guest is hvm, 1 if it's not. +function ishvm { + + if [[ $# != 1 ]]; then + echo "Need a guest name as an argument" + return 1; + fi + + if virsh dumpxml $1 | grep -q '>hvm<'; then + return 0 + else + return 1 + fi + +} +# function setvirttestargs : +# most virt testing scripts need to have guestnames to work with. This can be +# provided either at the workflow, or it can be determined during the testtime. +# If it's the latter, the test will run on each and every guest in order. The +# guest arguments will be separated with pipe sign , | +function setvirttestargs { + + if [[ -z ${VIRT_TESTARGS} ]]; then + for dir in /mnt/tests/distribution/virt/install/guests/* ; do + guest_name=$(basename $dir) + if [[ -z ${VIRT_TESTARGS} ]]; then + VIRT_TESTARGS="${guest_name}" + else + VIRT_TESTARGS="${VIRT_TESTARGS}|${guest_name}" + fi + done + fi + + echo ${VIRT_TESTARGS} + export VIRT_TESTARGS="${VIRT_TESTARGS}" + return 0 + + +} + +# function submitvirtlogs +# submits the logs to rhts based on what hypervisor is running +function submitvirtlogs { + + if iskvm; then + for kvmlog in $(find /var/log/libvirt/qemu/ -type f | tr '\n' "${IFS}") + do + rhts_submit_log -l ${kvmlog} + done + else + for xenlog in $(find /var/log/xen/ -type f | tr '\n' "${IFS}") + do + rhts_submit_log -l ${xenlog} + done + for dumps in $(find /var/lib/xen/dump -type f | tr '\n' "${IFS}") + do + thefile=$(basename $dumps) + if ! scp.exp -u netdump -p netdump -h $DUMPSERVER -t 3600 -f ${thefile} -F /var/crash/${JOBID}_${RECIPEID}_${thefile}; then + echo "problem with scp-ing $thefile " + else + echo "$thefile has been loaded up to netdump server, please investigate" + fi + done + fi + + #submit libvirtd debug log... + if [ -e /tmp/libvirtd_debug.log ]; then + rhts_submit_log -l /tmp/libvirtd_debug.log + # clean the log for the next test + echo "" > /tmp/libvirtd_debug.log + fi + + if setvirttestargs; then + + if [[ x"${VIRT_TESTARGS}" != "x" ]]; then + OLDIFS=${IFS} + IFS="|" + for guestname in $VIRT_TESTARGS + do + if [ -d $(pwd)/guests/${guestname}/logs ]; then + unset IFS + for file in $(ls $(pwd)/guests/${guestname}/logs) + do + rhts_submit_log -l $(pwd)/guests/${guestname}/logs/${file} + done + IFS="|" + fi + done + IFS=${OLDIFS} + fi + + fi + #submit dmesg + dmesg > ./dmesg.txt + rhts_submit_log -l ./dmesg.txt + # Always submit the audit.log + rhts_submit_log -l /var/log/audit/audit.log + +} + +function TurnOnLibvirtdLogging() +{ + if alias | grep cp=; then + unalias cp + fi + cp -f /etc/libvirt/libvirtd.conf /etc/libvirt/libvirtd.conf.orig + echo 'log_filters="1:libvirt 1:util 1:qemu"' >> /etc/libvirt/libvirtd.conf + echo 'log_outputs="1:file:/tmp/libvirtd_debug.log"' >> /etc/libvirt/libvirtd.conf + + if ! service libvirtd restart; then + echo "There was a problem restarting libvirtd!!!" + fi +} + + +function TurnOffLibvirtdLogging() +{ + + perl -pi.bak -e 's/^log_.*$//g' /etc/libvirt/libvirtd.conf + + if ! service libvirtd restart; then + echo "There was a problem restarting libvirtd!!!" + fi +} diff --git a/distribution/virt/import/runtest.sh b/distribution/virt/import/runtest.sh new file mode 100755 index 0000000..6872fd1 --- /dev/null +++ b/distribution/virt/import/runtest.sh @@ -0,0 +1,1112 @@ +#!/bin/bash + +# Source the common test script helpers +. /usr/bin/rhts_environment.sh +. /usr/local/bin/rhts_virt_funcs.sh + +result=PASS +value=0 +kvm_num=0 +home_basedir=0 +# control where to log debug messages to: +# devnull = 1 : log to /dev/null +# devnull = 0 : log to file specified in ${DEBUGLOG} +devnull=0 +mkdir -p /home/virtimages/VirtualMachines + +# Create debug log +DEBUGLOG=`mktemp -p /mnt/testarea -t virtinstall.XXXXXX` + +# locking to avoid races +lck=$OUTPUTDIR/$(basename $0).lck + +function TurnOnLibvirtdLogging() +{ + if alias | grep cp=; then + unalias cp + fi + cp -f /etc/libvirt/libvirtd.conf /etc/libvirt/libvirtd.conf.orig + echo 'log_filters="1:libvirt 1:util 1:qemu"' >> /etc/libvirt/libvirtd.conf + echo 'log_outputs="1:file:/tmp/libvirtd_debug.log"' >> /etc/libvirt/libvirtd.conf + + if ! service libvirtd restart; then + echo "There was a problem restarting libvirtd!!!" + fi +} + + +function TurnOffLibvirtdLogging() +{ + + perl -pi.bak -e 's/^log_.*$//g' /etc/libvirt/libvirtd.conf + + if ! service libvirtd restart; then + echo "There was a problem restarting libvirtd!!!" + fi +} + + + +# Log a message to the ${DEBUGLOG} or to /dev/null +function DeBug () +{ + local msg="$1" + local timestamp=$(date +%Y%m%d-%H%M%S) + if [ "$devnull" = "0" ]; then + echo -n "${timestamp}: " >>$DEBUGLOG 2>&1 + echo "${msg}" >>$DEBUGLOG 2>&1 + else + echo "${msg}" >/dev/null 2>&1 + fi +} + +function SelectKernel () +{ + DeBug "Enter SelectKernel" + VR=$1 + EXTRA=$2 + DeBug "VR=$VR EXTRA=$EXTRA" + + # If not version or Extra selected then choose the latest installed version + if [ -z "$EXTRA" -a -z "$VR" ]; then + DeBug "ERROR: missing args" + return 1 + fi + + # Workaround for broken RT kernel spec file, part 1 + if [ "$EXTRA" = "rt" ]; then + DeBug "EXTRA=$EXTRA" + EXTRA="" + fi + + # Workaround for broken RT kernel spec file, part 2 + if [ "$EXTRA" = "rt-vanilla" ]; then + DeBug "EXTRA=$EXTRA" + EXTRA="vanilla" + fi + + # Workaround for broken RT kernel spec file, part 1 + if [ "$EXTRA" = "up" ]; then + DeBug "EXTRA=$EXTRA" + EXTRA="" + fi + + echo "***** Attempting to switch boot kernel to ($VR$EXTRA) *****" + DeBug "Attempting to switch boot kernel to ($VR$EXTRA)" + + grub_file=/boot/grub/grub.conf + + if [ -f $grub_file ]; then + DeBug "Using: $grub_file" + COUNT=0 + DEFAULT=undefined + for i in $(grep '^title' $grub_file | sed 's/.*(\(.*\)).*/\1/'); do + DeBug "COUNT=$COUNT VR=$VR EXTRA=$EXTRA i=$i" + if echo $i | egrep -e "${VR}.*${EXTRA}" ; then + DEFAULT=$COUNT; + fi + COUNT=$(expr $COUNT + 1) + done + if [[ x"${DEFAULT}" != x"undefined" ]]; then + DeBug "DEFAULT=$DEFAULT" + /bin/ed -s $grub_file <<EOF +/default/ +d +i +default=$DEFAULT +. +w +q +EOF + fi + DeBug "$grub_file" + cat $grub_file | tee -a $DEBUGLOG + fi + + elilo_file=/boot/efi/efi/redhat/elilo.conf + + if [ -f $elilo_file ]; then + DeBug "Using: $elilo_file" + DEFAULT=$(grep -A 2 "image=vmlinuz-$VR$EXTRA$" $elilo_file | awk -F= '/label=/ {print $2}') + DeBug "DEFAULT=$DEFAULT" + if [ -n "$DEFAULT" ]; then + DeBug "DEFAULT=$DEFAULT" + /bin/ed -s $elilo_file <<EOF +/default/ +d +i +default=$DEFAULT +. +w +q +EOF + fi + DeBug "$elilo_file" + cat $elilo_file | tee -a $DEBUGLOG + fi + + yaboot_file=/boot/etc/yaboot.conf + + if [ -f $yaboot_file ] ; then + DeBug "Using: $yaboot_file" + grep vmlinuz $yaboot_file + if [ $? -eq 0 ] ; then + VM=z + else + VM=x + fi + DEFAULT=$(grep -A 1 "image=/vmlinu$VM-$VR$EXTRA" $yaboot_file | awk -F= '/label=/ {print $2}') + DeBug "DEFAULT=$DEFAULT" + if [ -n "$DEFAULT" ] ; then + sed -i 's/label=linux/label=orig-linux/g' $yaboot_file + sed -i 's/label='$DEFAULT'/label=linux/g' $yaboot_file + DeBug "DEFAULT=$DEFAULT" + grep -q label=linux $yaboot_file + if [ $? -ne 0 ] ; then + sed -i 's/label=orig-linux/label=linux/g' $yaboot_file + DeBug "Reverted back to original kernel" + fi + fi + DeBug "$yaboot_file" + cat $yaboot_file | tee -a $DEBUGLOG + fi + + zipl_file=/etc/zipl.conf + + if [ -f $zipl_file ] ; then + DeBug "Using: $zipl_file" + DEFAULT=$(grep "image=/boot/vmlinuz-$VR$EXTRA" $zipl_file | awk -Fvmlinuz- '/vmlinuz/ {printf "%.15s\n",$2}') + DeBug "DEFAULT=$DEFAULT" + if [ -n "$DEFAULT" ] ; then + DeBug "$VR$EXTRA" + tag=$(grep "\[$DEFAULT\]" $zipl_file) + DeBug "tag=$tag" + if [ -z "$tag" ] ; then + DeBug "Setting it back to default" + DEFAULT=linux + fi + /bin/ed -s $zipl_file <<EOF +/default=/ +d +i +default=$DEFAULT +. +w +q +EOF + zipl + fi + DeBug "$zipl_file" + cat $zipl_file | tee -a $DEBUGLOG + fi + + sync + sleep 5 + DeBug "Exit SelectKernel" + return 0 +} + + + + +function SubmitLog () +{ + LOG=$1 + rhts_submit_log -l $LOG +} + +function SubmitVirtLogs () +{ + # submit the relevant logfiles + if [[ ${kvm_num} > 0 ]]; then + for kvmlog in $(find /var/log/libvirt/qemu/ -type f) + do + rhts_submit_log -l ${kvmlog} + done + else + for xenlog in $(find /var/log/xen/ -type f) + do + rhts_submit_log -l ${xenlog} + done + for dumps in $(find /var/lib/xen/dump -type f) + do + rhts_submit_log -l ${dumps} + done + fi + + rhts_submit_log -l ${DEBUGLOG} + + #submit dmesg + dmesg > ./dmesg.txt + rhts_submit_log -l ./dmesg.txt + # Always submit the audit.log + rhts_submit_log -l /var/log/audit/audit.log + #submit virt-install log file + if [ -e ${HOME}/.virtinst/virt-install.log ]; then + rhts_submit_log -l ${HOME}/.virtinst/virt-install.log + fi + #submit libvirtd debug log... + if [ -e /tmp/libvirtd_debug.log ]; then + rhts_submit_log -l /tmp/libvirtd_debug.log + # clean the log for the next test + echo "" > /tmp/libvirtd_debug.log + fi + +} + +function setuprhel5consoles() +{ + local RESULT="PASS" + local FAIL=0 + + if ! cp zrhel5_write_consolelogs.initd /etc/init.d/zrhel5_write_consolelogs; then + echo "Problem copying zrhel5_write_consolelogs.initd to initd dir" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! cp zrhel5_write_consolelogs.py /usr/local/bin/zrhel5_write_consolelogs; then + echo "Problem copying zrhel5_write_consolelogs.py to /usr/local/bin" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + if ! chmod 755 /usr/local/bin/zrhel5_write_consolelogs; then + echo "Problem with chmoding zrhel5_write_consolelogs" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! chkconfig --add zrhel5_write_consolelogs; then + echo "problem with chkconfig --add zrhel5_write_consolelogs" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! chkconfig zrhel5_write_consolelogs on; then + echo "problem with chkconfig zrhel5_write_consolelogs on" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + iptables -I INPUT -p tcp --dport 8000 -j ACCEPT + iptables -I INPUT -p udp --dport 8000 -j ACCEPT + sleep 1 + if ! service iptables save; then + echo "Problem with service iptables save" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! cp -f iptables_after_libvirtd.initd /etc/init.d/iptables_after_libvirtd; then + echo "Problem with copying iptables_after_libvirtd.initd" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! chkconfig --add iptables_after_libvirtd; then + echo "problem with adding iptables_after_libvirtd" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! chkconfig iptables_after_libvirtd on; then + echo "problem with chkconfig iptables_after_libvirtd on" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! service iptables_after_libvirtd start; then + echo "problem with service iptables_after_libvirtd start" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + echo "Status of iptables: " + service iptables status + service zrhel5_write_consolelogs start + sleep 2 + echo "Status of rhel5_write_consoles:" + service zrhel5_write_consolelogs status + echo "Initial log output:" + cat /nohup.out + if [[ ${FAIL} > 0 ]]; then + report_result ${TEST}_zrhel5_write_consolelogs WARN $FAIL + return -1 + else + report_result ${TEST}_zrhel5_write_consolelogs PASS 0 + return 0 + fi + +} + +function setupconsolelogs() +{ + local RESULT="PASS" + local FAIL=0 + if ! gcc -g -Wall logguestconsoles.c -o logguestconsoles -lssl -lcrypto -lcurl $(xmlrpc-c-config client --libs) $(pkg-config libvirt --libs) $(xml2-config --cflags) $(xml2-config --libs); then + echo "Problem with compiling logguestconsoles.c file" + echo "Guest console logs won't be available" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! cp logguestconsoles /usr/local/bin; then + echo "Problem with copying logguestconsoles to /usr/local/bin" + echo "Guest console logs won't be available" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! cp logguestconsoles.initd /etc/init.d/logguestconsoles; then + echo "problem with copying init script" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if ! chkconfig --add logguestconsoles; then + echo "problem with chkconfig --add logguestconsoles" + RESULT="FAIL" + let "FAIL=${FAIL}+1" + fi + + if [[ ${FAIL} > 0 ]]; then + report_result ${TEST}_consolelogsetup WARN $FAIL + return 1 + else + report_result ${TEST}_consolelogsetup PASS 0 + return 0 + fi +} + +function setuprhel5_xmlrpcc() +{ + local SRCRPM="xmlrpc-c.el5.src.rpm" + local FAIL=0 + local OUTPUTLOG="./setuprhel5_xmlrpcc.log" + + if ! rpm -ivh ${LOOKASIDE}/${SRCRPM}; then + echo "problem with rpm -ivh ${LOOKASIDE}/${SRCRPM}" + FAIL=$(expr ${FAIL} + 1) + fi + + exec 5>&1 6>&2 + exec >> ${OUTPUTLOG} 2>&1 + + if ! rpmbuild -ba /usr/src/redhat/SPECS/xmlrpc-c.spec; then + echo "problem with rpmbuild -ba /usr/src/redhat/SPECS/xmlrpc-c.spec" + FAIL=$(expr ${FAIL} + 1) + fi + + if ! rpm -Uvh /usr/src/redhat/RPMS/$(arch)/xmlrpc-c*.rpm; then + echo "problem with rpm -Uvh /usr/src/redhat/RPMS/$(arch)/xmlrpc-c*.rpm" + FAIL=$(expr ${FAIL} + 1) + fi + + exec 1>&5 5>&- + exec 2>&6 6>&- + + if [[ ${FAIL} > 0 ]]; then + report_result ${TESTNAME}_setuprhel5_xmlrpcc FAIL 1 + else + report_result ${TESTNAME}_setuprhel5_xmlrpcc PASS 0 + fi + rhts_submit_log ${OUTPUTLOG} +} + + + +function rename_current_ifcfg_config() +{ + local cfg_file=/etc/sysconfig/network-scripts/ifcfg-$netdev + local ret=0 + if [ -e "$cfg_file" ]; then + echo "Found $cfg_file, trying to rename" + mv -vf $cfg_file /etc/sysconfig/network-scripts/Xifcfg-${netdev}.orig + ret=$? + else + # Bug 845225 - interface name does not match ifcfg- config file name + echo "Could not find $cfg_file, searching for one based on MAC" + local ifcfg_files=`grep -l "${mac}" /etc/sysconfig/network-scripts/ifcfg-*` + echo "Matching config files: $ifcfg_files" + for cfg_file in $ifcfg_files; do + bn=`basename $cfg_file` + mv -vf $cfg_file /etc/sysconfig/network-scripts/X$bn.orig + if [ $? -ne 0 ]; then + ret=1 + fi + done + fi + return $ret +} + +# Bug 901542 - qemu doesn't depend on seabios-bin, resulting in error: "qemu: PC system firmware (pflash) must be a multiple of 0x1000" +function workaround_bug901542() +{ + yum list installed seabios-bin + local ret2=$? + if [ $ret2 -ne 0 ]; then + echo "Bug 901542, seabios/seabios-bin is not installed, trying to install.." + yum -y install seabios seabios-bin + report_result bug901542 FAIL 1 + fi +} + +# Bug 958860 - Could not access KVM kernel module: Permission denied +function workaround_bug958860() +{ + local rights=`stat --format=%a /dev/kvm | tail -c 4` + if [ "${rights:0:1}" -lt "6" -o "${rights:1:1}" -lt "6" -o "${rights:2:1}" -lt "6" ]; then + echo "Bug 958860 - Could not access KVM kernel module, chmod-ing to 0666" + chmod 666 /dev/kvm + report_result bug958860 FAIL 1 + fi +} + +# Bug 957897 - service network start won't start network +function workaround_bug957897() +{ + if [ ! -e /etc/sysconfig/network ]; then + echo "Bug 957897 - service network start won't start network, touching /etc/sysconfig/network" + touch /etc/sysconfig/network + restorecon /etc/sysconfig/network + report_result bug957897 FAIL 1 + fi +} + +function ConfirmDefaultNetDevice () +{ + # RHEL5 brings up all the network devices and sets + # the last network device to come up as the default. + # ConfirmDefaultNetDevice: + # Confirms the network installation device + # is set as the default network device. + # This is required for proper network bridging on guests. + + # Run for RHEL5 only + local rhel5_ver="2.6.18" + local kernel_ver=`rpm -q --queryformat '%{version}\n' -qf /boot/config-$(uname -r)` + + if [ "$rhel5_ver" == "$kernel_ver" ]; then + echo "" + echo "***** RHEL5: Verifying the the network installation device is set as the default network device *****" + + if [ ! -e /root/anaconda-ks.cfg ]; then + echo "" + echo "***** WARN: /root/anaconda-ks.cfg file is missing *****" + echo "***** WARN: Unable to confirm system network installation device *****" + echo "" + report_result ${TEST}_ConfirmDefaultNetDevice WARN + else + echo "" + echo "***** Confirming system network installation device *****" + echo "***** Checking /root/anaconda-ks.cfg *****" + + grep "network --device" /root/anaconda-ks.cfg + if [ "$?" -ne "0" ]; then + echo "***** WARN: Unable to confirm system network installation device *****" + report_result ${TEST}_ConfirmDefaultNetDevice WARN + else + # Get system network installation device + local installdev=$(grep -oP "(?<=--device )[^ ]+" /root/anaconda-ks.cfg) + if [ "$?" -eq "0" ]; then + echo "***** System network installation device = $installdev *****" + echo "" + echo "***** Confirming system default network device *****" + echo "***** Checking route *****" + # Get systems current default network device + local defaultdev=$(route | grep default | awk '{print $NF}') + if [ "$?" -eq "0" ]; then + echo "***** Default network device = $defaultdev *****" + # Confirm install and default network device are the same device + if [ "$installdev" != "$defaultdev" ]; then + echo "***** The install and default network devices are not the same device *****" + echo "***** As RHEL5 sets the last device that comes up to the default, *****" + echo "***** its likely this system has multiple nics up on the same subnet *****" + echo "" + echo "***** Setting $installdev to default network device *****" + + # This trick will make the $installdev the last network device to come up + # Thus, resetting the RHEL5 default network device + ifdown "$installdev" + sleep 3 + ifup "$installdev" + + # One last check + if [ "$installdev" -ne "$defaultdev" ]; then + echo "***** WARN: Unable to set $installdev to default network device *****" + report_result ${TEST}_ConfirmDefaultNetDevice WARN + else + echo "***** $installdev successfully set to default network device *****" + echo "" + fi + else + echo "***** System installation and default default network device = $defaultdev *****" + echo "" + fi + else + echo "***** WARN: Unable to confirm default network device *****" + report_result ${TEST}_ConfirmDefaultNetDevice WARN + fi + fi + fi + fi + fi +} + +# +# ---------- Start Test ------------- +# + +# workaround RHEL7 issues +rpm -qa initscripts | grep "\.el7" +if [ $? -eq 0 ]; then + workaround_bug901542 + workaround_bug958860 + workaround_bug957897 +fi + +chmod 755 *.initd + +# turn on libvirtd debugging log. +TurnOnLibvirtdLogging + +## normally this test will run in selinux enforcing mode but sometimes we may +# want to run it in permissive mode to de bug selinux issues: +if [[ -n "${PERMISSIVE_MODE}" ]]; then + setenforce Permissive + if [[ $? != 0 ]] ; then + echo "Problem with setting enforcing to permissive" + report_result ${TEST}/selinux_permissive FAIL 1 + exit 0 + fi + perl -pi.bak -e 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config +fi + +# Add in a variable to workaround virt-install and selinux brokeness +# See BZ [Bug 475786] [RHEL5.3] SELinux AVC Denied: while trying to write to +# /.virtinst/virt-install.log +if [ -z $HOME ] ; then + export HOME=/root +fi + +# starting with rhel 5.4 we'll have both xen and kvm hypervisors shipped with the tree. +# This test will be used to install guests under both hypervisors hence it needs to +# identify what it is doing. @virtualization group pulls in packages/kernels for both +# hypervisors and by default kernel-xen is selected which means any guests installed will +# be xen guests. To allow testers to install kvm guestsi, --kvm argument in guest_args +# will be used. It will NOT be allowed to specify both xen or kvm guests. All guests must +# be of one hypervisor. The flow will be: +# +# Check if this is rhel6 installation or not. If it is, then just assume all kvm guests +# with or without --kvm indicators. +# +# If this is rhel5 then, figure what type of guests there are +# - Make sure that ALL guests are of the same type +# - if kvm: +# check the running kernel +# if baremetal, move on +# if xen, switch to baremetal kernel and rhts-reboot +# - if xen: +# check the running kernel +# if not kernel-xen error out (it should be the default) +# if xen, make changes to xend files and rhts-reboot +# - install guests. +if ver=$(rpm -q --qf '%{version}\n' --whatprovides redhat-release); then + if [[ ${ver} == 6 || ${ver} > 6 ]]; then + kvm_num=1 + else + # ensure that they all are either xen or kvm + if ! kvm_num=$(./get_guest_info.py --kvm-num) ; then + echo "can't mix up kvm and non-kvm guests. They all have to be kvm or nonkvm" + report_result ${TEST}_wrongguestsetup FAIL 1 + submitvirtlogs + exit 1 + fi + fi +fi + +# if this will be kvm guests' install make sure that correct kernel is running. +if [[ ${kvm_num} > 0 ]]; then + if uname -r | grep xen ; then + xenkern=$(uname -r) + basekern=${xenkern%"xen"} + # if for whatever reason base kernel isn't installed, install it.. + if ! rpm -q kernel-${basekern}; then + yum -y install kernel-${basekern} kvm + fi + # make sure that yum installed it.. + if ! rpm -q kvm; then + echo "Can't find/install kvm" + report_result ${TEST}_nokvm FAIL 1 + exit 1 + fi + echo "this test seems to be for kvm guests, booting into vanilla kernel" + SelectKernel ${basekern} + echo "Rebooting into base kernel since this is kvm guest" + report_result rhts-reboot PASS $REBOOTCOUNT + rhts-reboot + fi + + if [[ ${ver} == 6 || ${ver} > 6 ]]; then + # for rhel6 & above we need to set up bridging and get network manager out + # of the way... + if [[ ${REBOOTCOUNT} == 0 ]]; then + + ## we need to configure/add bridge to establish bridged networking for + ## kvm guests + def_line=$(ip route list | grep ^default) + defnum=$(perl -e 'for ($i=0; $i<$#ARGV; $i++ ) { if ($ARGV[$i] eq "dev" ) { $_ = $ARGV[ $i + 1 ]; if ( /^(\w*)(\d+)/ ) { print "$_ $2"; } } }' ${def_line} ) + actnum=$(echo ${defnum} | awk '{print $2}') + netdev=$(echo ${defnum} | awk '{print $1}') + vifnum=${vifnum:-$actnum} + if [ -z ${vifnum} ]; then + echo "Can't get the interface number " + report_result ${TEST}_networksetup FAIL 1 + exit 1 + fi + brdev="br${vifnum}" + pdev="p${netdev}" + mac=`ip link show ${netdev} | grep 'link\/ether' | sed -e 's/.*ether \(..:..:..:..:..:..\).*/\1/'` + if [ -z ${mac} ]; then + echo "Can't find the mac address" + report_result ${TEST}_networksetupnomac FAIL 1 + exit 1 + fi + echo "brdev: ${brdev} netdev: ${netdev} pdev: ${pdev} mac: ${mac} " + + rename_current_ifcfg_config + if [[ $? != 0 ]]; then + echo "Problem copying network config scripts" + report_result ${TEST}_networksetup FAIL 1 + exit 1 + fi + cat <<EOF > /etc/sysconfig/network-scripts/ifcfg-$netdev +DEVICE=$netdev +ONBOOT=yes +BRIDGE=$brdev +HWADDR=$mac +EOF + + cat <<EOF > /etc/sysconfig/network-scripts/ifcfg-$brdev +DEVICE=$brdev +BOOTPROTO=dhcp +ONBOOT=yes +TYPE=Bridge +DELAY=0 +EOF + + service NetworkManager stop + chkconfig NetworkManager off + chkconfig network on + service network restart + if [[ $? != 0 ]]; then + echo "problem restarting network" + + rpm -qa initscripts | grep "\.el7" + if [ $? -eq 0 ]; then + echo "This is known RHEL7 issue, proceeding anyway.." + echo "Bug 886090 - ifcfg- config contains ONBOOT=yes for interface with no link" + report_result ${TEST}_networksetup FAIL 1 + else + report_result ${TEST}_networksetup FAIL 1 + exit 1 + fi + else + echo "configured a bridge: $netdev " + fi + + echo "Rebooting after configuring the bridge" + report_result rhts-reboot PASS $REBOOTCOUNT + rhts-reboot + + # when it's already set up and rebooted, then get the bridge to use to + # pass it on to virt-install + else + def_line=$(ip route list | grep ^default) + defnum=$(perl -e 'for ($i=0; $i<$#ARGV; $i++ ) { if ($ARGV[$i] eq "dev" ) { $_ = $ARGV[ $i + 1 ]; if ( /^(\w*)(\d+)/ ) { print "$_ $2"; } } }' ${def_line} ) + actnum=$(echo ${defnum} | awk '{print $2}') + netdev=$(echo ${defnum} | awk '{print $1}') + vifnum=${vifnum:-$actnum} + if [ -z ${vifnum} ]; then + echo "Can't get the interface number " + report_result ${TEST}_networksetup FAIL 1 + exit 1 + fi + brdev="br${vifnum}" + fi + + else + + ## we need to configure/add bridge to establish bridged networking for + ## kvm guests + + # For RHEL5 before we establish a network bridge device + # Lets confirm the default network device is correct + ConfirmDefaultNetDevice + + def_line=$(ip route list | grep ^default) + defnum=$(perl -e 'for ($i=0; $i<$#ARGV; $i++ ) { if ($ARGV[$i] eq "dev" ) { $_ = $ARGV[ $i + 1 ]; if ( /^(\w*)(\d+)/ ) { print "$_ $2"; } } }' ${def_line} ) + actnum=$(echo ${defnum} | awk '{print $2}') + netdev=$(echo ${defnum} | awk '{print $1}') + vifnum=${vifnum:-$actnum} + if [ -z ${vifnum} ]; then + echo "Can't get the interface number " + report_result ${TEST}_networksetup FAIL 1 + exit 1 + fi + brdev="br${vifnum}" + pdev="p${netdev}" + mac=`ip link show ${netdev} | grep 'link\/ether' | sed -e 's/.*ether \(..:..:..:..:..:..\).*/\1/'` + if [ -z ${mac} ]; then + echo "Can't find the mac address" + report_result ${TEST}_networksetupnomac FAIL 1 + exit 1 + fi + echo "brdev: ${brdev} netdev: ${netdev} pdev: ${pdev} mac: ${mac} " + + rename_current_ifcfg_config + if [[ $? != 0 ]]; then + echo "Problem copying network config scripts" + report_result ${TEST}_networksetup FAIL 1 + exit 1 + fi + cat <<EOF > /etc/sysconfig/network-scripts/ifcfg-$netdev +DEVICE=$netdev +ONBOOT=yes +BRIDGE=$brdev +HWADDR=$mac +EOF + + cat <<EOF > /etc/sysconfig/network-scripts/ifcfg-$brdev +DEVICE=$brdev +BOOTPROTO=dhcp +ONBOOT=yes +TYPE=Bridge +DELAY=0 +EOF + + + service NetworkManager stop + chkconfig NetworkManager off + chkconfig network on + service network restart + if [[ $? != 0 ]]; then + echo "problem restarting network" + report_result ${TEST}_networksetup FAIL 1 + exit 1 + else + echo "configured a bridge: $netdev " + fi + + fi + + # see BZ# 749611 + if rpm -q kernel-xen; then + yum -y erase kernel-xen + fi + +else # this is a xen install + + # turn on various logs in xend and restart xend + if [[ $REBOOTCOUNT == 0 ]]; then + perl -pi.bak -e 's/#*\(enable-dump\s+no\)/(enable-dump yes)/g' /etc/xen/xend-config.sxp + perl -pi.bak -e 's/^#*XENCONSOLED_LOG_HYPERVISOR=no/XENCONSOLED_LOG_HYPERVISOR=yes/g;s/^#*XENCONSOLED_LOG_GUESTS=no/XENCONSOLED_LOG_GUESTS=yes/g;s/^#*XENCONSOLED_LOG_DIR=.*$/XENCONSOLED_LOG_DIR=\/var\/log\/xen\/console/g' /etc/sysconfig/xend + echo "Reboot needed to enable xen logging" + report_result rhts-reboot PASS $REBOOTCOUNT + rhts-reboot + else + echo "Reboot was successful" + + # For RHEL5 before we establish a network bridge device + # Lets confirm the default network device is correct + ConfirmDefaultNetDevice + fi + + # are we running on a Xen kernel in domain 0 ? + # (only report if RHTS tried running us on an unsuitable host) + # + if [ -d /proc -a ! -f /proc/xen/privcmd ] ; then + DeBug "Don't think we are on a Dom0" + echo "Don't think we are on a Dom0" + report_result ${TEST} WARN 10 + exit 0 + fi +fi + +## we need libvirt-devel, that might not be installed during installation time +if ! rpm -q libvirt-devel; then + yum -y install libvirt-devel + if ! rpm -q libvirt-devel; then + echo "can't install libvirt-devel" + echo "guest console logs might not work" + report_result NO_libvirt-devel WARN 10 + fi +fi + +echo "***********************" +echo "* SELinux Status *" +echo "***********************" +/usr/sbin/sestatus +virtinst="virt-install" + +if [[ $REBOOTCOUNT > 1 ]]; then + echo "Looks like dom0 rebooted during a guest install. Check console logs" + report_result ${TEST}_dom0rebooted FAIL 99 + submitvirtlogs + exit 0 +fi + +./get_guest_info.py > ./tmp.guests +echo "Guests info:" +cat ./tmp.guests + +# go thru the guests and set up console sniff/upload +while read -r guest_recipeid guest_name guest_mac guest_args ; do + if ! mkdir -p $(pwd)/guests/${guest_name}/logs; then + report_result ${TEST}_cant_create_dirs FAIL 10 + fi + guest_con_logfile="$(pwd)/guests/${guest_name}/logs/${guest_name}_console.log" + echo "$guest_con_logfile $guest_recipeid" >> /usr/local/etc/logguestconsoles.conf + if [ ! -e $guest_con_logfile ] ; then + touch $guest_con_logfile + fi + chmod a+rw $guest_con_logfile +done < ./tmp.guests + +# setup consolelogging +# logguestconsoles create files itself, so if qemu has issues with +# that in future (permissions/selinux), this needs to be started +# only after qemu creates those files +# +# on some rhel5 releases xmlrpc-c package doesn't exist, install it here +if [ ${ver:0:1} -lt 6 ]; then + minor_ver=$(sed 's/.* release 5\.\([0-9]*\) .*/\1/' /etc/redhat-release) + if [[ ${minor_ver} < 6 && ${minor_ver} > 3 ]]; then + setuprhel5_xmlrpcc + fi +fi + +if setupconsolelogs; then + service logguestconsoles start + sleep 2 + echo "Status of logguestconsoles:" + service logguestconsoles status + echo "Initial log output:" + cat /var/log/logguestconsoles.* +fi + +while read -r guest_recipeid guest_name guest_mac guest_args ; do + DeBug "guest is : + guest_recipeid=$guest_recipeid + guest_name=$guest_name + guest_mac=$guest_mac + guest_args=$guest_args" + if [ -z "$guest_name" ] ; then + echo "get_guest_info.py did not return a guest name" + report_result ${TEST}_no_guestname FAIL 10 + exit 1 + fi + if ! mkdir -p $(pwd)/guests/${guest_name}/logs || ! mkdir -p $(pwd)/guests/${guest_name}/iso; then + echo "Problem creating $(pwd)/guests/${guest_name}/{logs,iso} dirs" + report_result ${TEST}_cant_create_dirs FAIL 10 + exit 1 + fi + if ! chmod -R 777 $(pwd)/guests/${guest_name}; then + echo "problem with chmod -R 777 $(pwd)/guests/${guest_name}" + report_result ${TEST}_chmod_issue FAIL 10 + exit 1 + fi + + if [ -n "$*" ]; then + guest_args=$* + fi + + # decide where to put the guest's image based on availability + home_basedir=0 + var_lib_df=$(df /var/lib) + var_lib_free=$(echo ${var_lib_df} | awk '{print $11}') + home_df=$(df /home) + home_free=$(echo ${home_df} | awk '{print $11}') + if [[ ${home_free} -gt ${var_lib_free} ]]; then + mkdir -p /home/virtimages/VirtualMachines + home_basedir=1 + fi + + if [[ ${kvm_num} > 0 ]]; then + if [[ ${home_basedir} == 0 ]]; then + basedir="/var/lib/libvirt/images" + else + basedir="/home/virtimages/VirtualMachines" + fi + else + if [[ ${home_basedir} == 0 ]]; then + basedir="/var/lib/xen/images" + else + basedir="/home/virtimages/VirtualMachines" + fi + fi + + # Retrieve guest image from Lookaside + if ! wget -O - $LOOKASIDE/virt_images/${guest_name}.img.bz2 | bzip2 -cd > $basedir/${guest_name}.img; then + echo "Unable to download/uncompress $LOOKASIDE/virt_images/${guest_name}.img.bz2" + report_result ${TEST}/NoImage FAIL 100 + exit 1 + fi + + #bridge=$(ip route list | awk '/^default / { print $NF }' | sed 's/^[^0-9]*//') + #CMDLINE="-b xenbr${bridge} -n ${guestname} -f ${IMAGE} $args" + CMDLINE="--name ${guest_name} --mac ${guest_mac} $guest_args --debug --nographics --noreboot --import" + if [[ ${kvm_num} > 0 ]]; then + CMDLINE="${CMDLINE}" + else + ## the first 2 conditions are for fedora releases since they too can support xen guests. + if grep -q -i fedora /etc/fedora-release; then + CMDLINE="$CMDLINE --serial file,path=$(pwd)/guests/${guest_name}/logs/${guest_name}_console.log --extra-args \"ks=$guest_ks serial console=tty0 console=ttyS0,115200\"" + fi + fi + + CMDLINE="${CMDLINE} --disk path=$basedir/${guest_name}.img,xvda,w" + + # FIXME: Update image to use guest recipe id + + # kvm guest should have --accelerate and --os-variant=virtio26 by default. + # --kvm switch shouldn't be passed on to virtinstall.exp + if [[ ${kvm_num} > 0 ]]; then + #get rid of --kvm + CMDLINE=$( echo ${CMDLINE} | awk '{ for (i=1;i<=NF;i++) { if ( $i != "--kvm" ) printf "%s ", $i } }' ) + #add --accelerate or --os-variant=virtio26 or both + echo ${CMDLINE} | awk '{rc=0; for(i=1;i<=NF;i++) { if ( $i ~ /--accelerate*/ ) rc+=1; else if ( $i ~ /--os-variant=*/ ) rc += 2; } exit rc }' + rc=$? + if [[ $rc == 0 ]]; then + CMDLINE="${CMDLINE} --accelerate --os-variant=virtio26 " + elif [[ $rc == 1 ]]; then + CMDLINE="${CMDLINE} --accelerate " + elif [[ $rc == 3 ]]; then + CMDLINE="${CMDLINE} --os-variant=virtio26 " + fi + # make an exception for given --network arg.. + # see BZ#821984 + GIVENNW=$(echo $CMDLINE | awk '{ for (i=1;i<=NF;i++) { if ( $i == "--network" ) { i+=1; printf "%s ", $i } else { continue; } } }') + if [[ -z "${GIVENNW}" ]]; then + CMDLINE="${CMDLINE} --ver6 --network bridge:${brdev} " + else + CMDLINE=$(echo $CMDLINE | awk '{ for (i=1;i<=NF;i++) { if ( $i == "--network" ) { i+=1; continue; } else printf "%s ", $i } }') + NWARG="" + for opts in ${GIVENNW} + do + NWARG="${NWARG} --network bridge:${brdev},${opts}" + done + CMDLINE="${CMDLINE} --ver6 ${NWARG}" + + fi + + # beginning with rhel6 virt-install can take -serial option. automatically + # append serial console args unless one is given already or unless --virttest is given + if [[ ${ver} == 6 || ${ver} > 6 ]]; then + echo ${CMDLINE} | awk '{rc=0; for(i=1;i<=NF;i++) { if ( $i == "--virttest" || $i ~ /--serial*/) exit 1 } exit 0 }' + rc=$? + if [[ ${rc} == 0 ]]; then + CMDLINE="$CMDLINE --serial file,path=$(pwd)/guests/${guest_name}/logs/${guest_name}_console.log" + # workaround for BZ: 731115 + l_guest_name=$(echo ${guest_name} | tr [:upper:] [:lower:]) + if [[ x"$guest_name" != x"$l_guest_name" ]]; then + ln -sf $(pwd)/guests/${guest_name} $(pwd)/guests/${l_guest_name} + if [[ $? != 0 ]]; then + echo "error with workaround for bz731115" + report_result ${TEST}_logdir_link FAIL 100 + fi + fi + # end of workaround for BZ 731115 + # + fi + fi + fi + + # Tell Beaker the guest recipe has started. + ./start_recipe.py $guest_recipeid + + DeBug "CMDLINE == $CMDLINE" + echo "CMDLINE == $CMDLINE" + DeBug "***** Start $virtinst ${CMDLINE} *****" + echo "***** Start $virtinst ${CMDLINE} *****" + starttime=$(date +%s) + echo "Start time: $starttime" + if selinuxenabled; then + eval /usr/bin/runcon -t unconfined_t -- $virtinst $CMDLINE 2>&1 + else + eval $virtinst $CMDLINE 2>&1 + fi + value=$? + endtime=$(date +%s) + echo "End time: $endtime" + if [[ $value == 1 ]]; then + echo "WARNING: install may or may not have failed. Check the guest" + let "fail=${fail}+1" + result=FAIL + report_result ${TEST}/install_${guest_name} $result $value + elif [[ $value == 14 ]]; then + echo "Err No 14 workaround" + let "fail=${fail}+1" + result=WARN + report_result ${TEST}/install_${guest_name} $result $value + elif [[ $value == 33 ]]; then + echo "libvirt error" + let "fail=${fail}+1" + result=FAIL + report_result ${TEST}/install_${guest_name}_libvirterror $result $value + elif [[ $value == 37 ]]; then + echo "guest crashed" + let "fail=${fail}+1" + result=FAIL + report_result ${TEST}/install_${guest_name}_guestcrash $result $value + elif [[ $value == 38 ]]; then + echo "no HVM support on the machine" + let "fail=${fail}+1" + result=FAIL + report_result ${TEST}/install_${guest_name}_noHVMsupport $result $value + elif [[ $value != 0 ]]; then + echo "$virtinst FAILED" + let "fail=${fail}+1" + result=FAIL + report_result ${TEST}/install_${guest_name} $result $value + else + result=PASS + elapsed=$(expr $endtime - $starttime) + echo "***** Finished $virtinst ${guest_name} in $elapsed seconds *****" + report_result ${TEST}/import_${guest_name} $result $elapsed + fi + # and for rhel6 and above we should have a console log. + if [ -e `pwd`/guests/${guest_name}/logs/${guest_name}_console.log ]; then + rhts_submit_log -l `pwd`/guests/${guest_name}/logs/${guest_name}_console.log + fi + # upload the guest's config file too. + if [[ ${kvm_num} < 1 ]]; then + rhts_submit_log -l /etc/xen/${guest_name} + elif virsh dumpxml ${guest_name}; then + virsh dumpxml ${guest_name} > ./guests/${guest_name}/logs/${guest_name}.xml + rhts_submit_log -l ./guests/${guest_name}/logs/${guest_name}.xml + fi + let i="$i+1" +done < ./tmp.guests +if [[ $i == 0 ]]; then + report_result ${TEST}/noguestinstall PASS 0 +elif [[ ${fail} == 0 ]]; then + report_result ${TEST} PASS 0 +else + report_result ${TEST} FAIL 1 +fi + +# turn on service for rhel5 console writing. +# This is after the guests are installed so that it won't try to steal console +# from the installation +if [ ${ver:0:1} -lt 6 -a ${minor_ver} -gt 3 -a -z "${NORHEL5CONSOLELOGS}" ]; then + setuprhel5consoles +fi + +# submit the relevant logfiles +submitvirtlogs +if [ -e /nohup.out ]; then + rhts_submit_log -l /nohup.out +fi + +exit 0 diff --git a/distribution/virt/import/scp.exp b/distribution/virt/import/scp.exp new file mode 100755 index 0000000..033aed7 --- /dev/null +++ b/distribution/virt/import/scp.exp @@ -0,0 +1,62 @@ +#!/usr/bin/expect +# +# Usage: scp.exp <-f File> <-h Remotehost> [-u User] [-p Password] [-t timeout] +# +# username, password, timeout are optional. + +set USAGE_STR "$argv0 <-f|--file LocalFile> <-h|--host Remotehost> \[-F|--File \ +remotefile\] \[-u|--user User\] \[-p|--password Password\] \[-t|--timeout \ +timeout\] \n" +set username "root" +set password "redhat" +set remotefile "~/." +if { $argc < 4 || $argc > 12 } { + send_user "Usage: ${USAGE_STR} \n" + exit 1 +} + +for {set i 0} { $i < $argc } { incr i } { + + set cur_arg [lindex $argv $i] + if { $cur_arg == "-f" || $cur_arg == "--file" } { + set localfile [lindex $argv [incr i] ] + } elseif { $cur_arg == "-h" || $cur_arg == "--host" } { + set remotehost [lindex $argv [incr i] ] + } elseif { $cur_arg == "-F" || $cur_arg == "--File" } { + set remotefile [lindex $argv [incr i] ] + } elseif { $cur_arg == "-u" || $cur_arg == "--user" } { + set username [lindex $argv [incr i] ] + } elseif { $cur_arg == "-p" || $cur_arg == "--password" } { + set password [lindex $argv [incr i] ] + } elseif { $cur_arg == "-t" || $cur_arg == "--timeout" } { + set timeout [lindex $argv [incr i] ] + } else { + send_user "unknown arg: $cur_arg \n" + send_user "Usage: ${USAGE_STR} \n" + exit 1 + } + +} + +if { ![info exists localfile] } { + send_user "No file option is given!!!\n"; + send_user "Usage: ${USAGE_STR} \n" + exit 1 +} elseif { ![info exists remotehost] } { + send_user "No file option is given!!!\n"; + send_user "Usage: ${USAGE_STR} \n" + exit 1 +} + + +spawn scp $localfile ${username}@${remotehost}:${remotefile} +set scp_spawn $spawn_id +expect { + -i $scp_spawn "connecting (yes/no)? " { send -i $scp_spawn "yes\n"; exp_continue; } + -i $scp_spawn -re "(P|p)assword: " { send -i $scp_spawn "$password\n"; exp_continue; } + -i $scp_spawn "No such file or directory" { send_user "No such file or dir\n"; exit 1; } + -i $scp_spawn "Permission denied," {send_user "wrong password, exiting.\n"; exit 1; } + -i $scp_spawn -exact "100%" { exp_continue } + -i $scp_spawn timeout { send_user "expect timeout!\n"; exit 1} + -i $scp_spawn eof { exit 0 } +} diff --git a/distribution/virt/import/start_recipe.py b/distribution/virt/import/start_recipe.py new file mode 100755 index 0000000..479e3ed --- /dev/null +++ b/distribution/virt/import/start_recipe.py @@ -0,0 +1,23 @@ +#!/usr/bin/python + +""" +Marks a guest recipe as started. Pass the recipe ID as the first argument. +""" + +import os +import sys +import xmlrpclib +import xml.dom.minidom + +proxy = xmlrpclib.ServerProxy('http://%s:8000/RPC2' % os.environ['LAB_CONTROLLER']) +recipe_id = int(sys.argv[1]) +recipe_xml = proxy.get_my_recipe(dict(recipe_id=recipe_id)) +doc = xml.dom.minidom.parseString(recipe_xml) + +# This is the same logic as when the 'reboot' command goes through for a real +# system... grab the first task in the recipe and mark it as started. This will +# set the state of the recipe to Running and start the watchdog with some time +# on the clock. The watchdog will be extended again once Anaconda hits +# install_start. +first_task = doc.getElementsByTagName('task')[0] +proxy.task_start(first_task.getAttribute('id')) diff --git a/distribution/virt/import/startguest b/distribution/virt/import/startguest new file mode 100755 index 0000000..be593ea --- /dev/null +++ b/distribution/virt/import/startguest @@ -0,0 +1,61 @@ +#!/usr/bin/expect +# +# utility script to start the guest and make sure that it started is up +# it has a default of 5 minutes to wait for the guest to start up. +# + +if { $argc < 1 } { + send_user "Usage: $argv0 guestname <optional timeout value>" + exit 1 +} + +# stupid hack for when this is called from a script +if { $argc == 1 } { + set argv_tmp [lindex $argv 0] + set argv [split $argv_tmp] +} + +if { [llength $argv] > 1 } { + set waittimeout [lindex $argv 1] +} else { + set waittimeout 300 +} + +set guest [lindex $argv 0] +set prompt "(%|#|\\\$) $" +log_user 0 + +spawn virsh start $guest +set start_spawn $spawn_id + +expect { + -i $start_spawn "Domain $guest started" { exp_continue; } + -i $start_spawn timeout { + send_user "timeout on virsh start $guest\n"; + exit 1; + } + -i $start_spawn eof { } +} + +spawn virsh console $guest +set con_spawn $spawn_id +set timeout $waittimeout + +send -i $con_spawn "\r" +send -i $con_spawn "\r" +send -i $con_spawn "\r" +expect { + -i $con_spawn "login: " { send -i $con_spawn ; exit 0; } + -i $con_spawn -re "(P|p)assword:" { send -i $con_spawn ; exit 0; } + -i $con_spawn -re $prompt { send -i $con_spawn ; exit 0; } + -i $con_spawn timeout { + send_user "start timeout\n"; + exit 1; + } + -i $con_spawn eof { + send_user "start EOF\n"; + exit 1; + } +} + +exit 1; diff --git a/distribution/virt/import/stopguest b/distribution/virt/import/stopguest new file mode 100755 index 0000000..b4d20f3 --- /dev/null +++ b/distribution/virt/import/stopguest @@ -0,0 +1,56 @@ +#!/usr/bin/expect +# +# utility script to stop the guest and make sure that it has shutdown +# it has a default of 5 minutes to wait for the guest to shut +# + +if { $argc < 1 } { + send_user "Usage: $argv0 guestname <optional timeout value>" + exit 1 +} + +# stupid hack for when this is called from a script +if { $argc == 1 } { + set argv_tmp [lindex $argv 0] + set argv [split $argv_tmp] +} + +if { [llength $argv] > 1 } { + set waittimeout [lindex $argv 1] +} else { + set waittimeout 300 +} + +set guest [lindex $argv 0] +set prompt "(%|#|\\\$) $" +log_user 0 + +spawn virsh shutdown $guest +set stop_spawn $spawn_id + +expect { + -i $stop_spawn "Domain $guest is being shutdown" { } + -i $stop_spawn timeout { + send_user "timeout on virsh shutdown $guest\n"; + exit 1; + } + -i $stop_spawn eof { + send_user "EOF on virsh shutdown $guest\n"; + exit 1; + } +} + +spawn virsh console $guest +set con_spawn $spawn_id +set timeout $waittimeout + +expect { + -i $con_spawn "System halted." { exit 0; } + -i $con_spawn timeout { + send_user "start timeout\n"; + exit 1; + } + -i $con_spawn eof { exit 0; } +} + +exit 1; diff --git a/distribution/virt/import/virshstartguest b/distribution/virt/import/virshstartguest new file mode 100755 index 0000000..4ed37c0 --- /dev/null +++ b/distribution/virt/import/virshstartguest @@ -0,0 +1,68 @@ +#!/usr/bin/expect +# +# utility script to start the guest and make sure that it started is up +# it has a default of 5 minutes to wait for the guest to start up. +# + +#exp_internal 1 + +if { $argc < 1 } { + send_user "Usage: $argv0 guestname <optional timeout value>" + exit 1 +} + +# stupid hack for when this is called from a script +if { $argc == 1 } { + set argv_tmp [lindex $argv 0] + set argv [split $argv_tmp] +} + +if { [llength $argv] > 1 } { + set waittimeout [lindex $argv 1] +} else { + set waittimeout 300 +} + +set guest [lindex $argv 0] +set prompt "(%|#|\\\$) $" +log_user 0 + +spawn virsh start $guest +set start_spawn $spawn_id +expect { + -i $start_spawn "Domain $guest started" { } + -i $start_spawn timeout { + send_user "timeout on virsh start $guest\n"; + exit 1; + } + -i $start_spawn eof { + send_user "Unexpected EOF on starting $guest \n"; + exit 1; + } +} + +sleep 15 +set timeout $waittimeout +spawn virsh console $guest +set con_spawn $spawn_id +send -i $con_spawn "\n" +send -i $con_spawn "\n" +send -i $con_spawn "\n" +expect { + -i $con_spawn "Press any key to continue" { send -i $con_spawn "\n"; exp_continue; } + -i $con_spawn "Press enter" {sleep 5 ; send -i $con_spawn "\n"; exp_continue; } + -i $con_spawn "The highlighted entry will be booted automatically in" { exp_continue; } + -i $con_spawn -exact {[ OK ]} { exp_continue; } + -i $con_spawn "login:" { send -i $con_spawn "\n";send -i $con_spawn ; exit 0; } + -i $con_spawn -re "(P|p)assword:" { send -i $con_spawn "\n";send -i $con_spawn ; exit 0; } + -i $con_spawn timeout { + send_user "start timeout\n"; + exit 1; + } + -i $con_spawn eof { + send_user "start EOF\n"; + exit 1; + } +} + +exit 1; diff --git a/distribution/virt/import/virshstopguest b/distribution/virt/import/virshstopguest new file mode 100755 index 0000000..b4d20f3 --- /dev/null +++ b/distribution/virt/import/virshstopguest @@ -0,0 +1,56 @@ +#!/usr/bin/expect +# +# utility script to stop the guest and make sure that it has shutdown +# it has a default of 5 minutes to wait for the guest to shut +# + +if { $argc < 1 } { + send_user "Usage: $argv0 guestname <optional timeout value>" + exit 1 +} + +# stupid hack for when this is called from a script +if { $argc == 1 } { + set argv_tmp [lindex $argv 0] + set argv [split $argv_tmp] +} + +if { [llength $argv] > 1 } { + set waittimeout [lindex $argv 1] +} else { + set waittimeout 300 +} + +set guest [lindex $argv 0] +set prompt "(%|#|\\\$) $" +log_user 0 + +spawn virsh shutdown $guest +set stop_spawn $spawn_id + +expect { + -i $stop_spawn "Domain $guest is being shutdown" { } + -i $stop_spawn timeout { + send_user "timeout on virsh shutdown $guest\n"; + exit 1; + } + -i $stop_spawn eof { + send_user "EOF on virsh shutdown $guest\n"; + exit 1; + } +} + +spawn virsh console $guest +set con_spawn $spawn_id +set timeout $waittimeout + +expect { + -i $con_spawn "System halted." { exit 0; } + -i $con_spawn timeout { + send_user "start timeout\n"; + exit 1; + } + -i $con_spawn eof { exit 0; } +} + +exit 1; diff --git a/distribution/virt/import/wait4guest b/distribution/virt/import/wait4guest new file mode 100755 index 0000000..00af187 --- /dev/null +++ b/distribution/virt/import/wait4guest @@ -0,0 +1,51 @@ +#!/usr/bin/python + +import xmltramp +import xmlrpclib +import time +import sys +from beah import config +from datetime import datetime, timedelta + +lab_controller = None + +USAGE_TEXT = """ +Usage: wait4guest guestrecipeid <timeout> + +timeout is optional and is in seconds +""" + +def configure(): + config.backend_conf(filename='beah_beaker.conf') + +def loop(recipeid, timeout): + conf = config.get_conf('beah-backend') + lab_controller = conf.get('DEFAULT','LAB_CONTROLLER') + server = xmlrpclib.ServerProxy(lab_controller) + while True: + myxml = server.get_my_recipe(dict(recipe_id=recipeid)) + status = xmltramp.parse(myxml).recipeSet.guestrecipe()['status'] + if status in ['Running']: + sys.exit(0) + if timeout and timeout < datetime.now(): + print "Timeout Exceeded" + sys.exit(1) + time.sleep(60) + +def usage(): + sys.stderr.write(USAGE_TEXT) + sys.exit(-1) + +def main(): + if len(sys.argv) < 2: + usage() + recipeid = sys.argv[1] + if len(sys.argv) == 3: + timeout = datetime.now() + timedelta(seconds = int(sys.argv[2])) + else: + timeout = None + configure() + loop(recipeid, timeout) + +if __name__ == '__main__': + main() diff --git a/distribution/virt/import/wait4guesttasks b/distribution/virt/import/wait4guesttasks new file mode 100755 index 0000000..91484f7 --- /dev/null +++ b/distribution/virt/import/wait4guesttasks @@ -0,0 +1,201 @@ +#!/usr/bin/python +# + +# wait4guesttasks +# usage: wait4guesttasks <--guestname name | --guestrecipe recipe> \ +# [--timeout NN[s|m|h|d]] [--interval NN[s|m|h|d]] \ +# [/task/name /task2/name ...] +# this script will take a name or recipeid of the guest and list of task to be +# waited to for completion and a timeout value to wait for all tasks to be +# completed. If the tasks inside the guest complete with the optional statuses, +# it'll exit with exit code of 0. If anything goes wrong, it'll exit with an +# exit code other than zero. timeout argument can take an optional and +# case-insensitive suffix of s,m,h,d corresponding to seconds,minutes,hours,days +# respectively. +# Everything but either guestname or guestrecipeid is optional. Default values +# follows: +# timeout: none. ie. it'll run forever until the tasks are completed. +# interval: set to 120 seconds. +# tasks: default is all tasks inside the guest to be completed. +# Additionally, a desired result for any task can be given. For that the syntax +# is : +# taskname=result for example /distribution/install=Pass +# If no desired status are given, then the script will just wait until the task +# is completed. For example the following syntax: +# .waitforguesttasks.py --guestname myguest /distribution/install=Pass\ +# /my/test +# will wait until /distribution/install test inside myguest has completed and +# Passed AND the test /my/test has completed. If /distribution/install has a +# result other than Pass, it'll exit with an error code. The results are +# case-insensitive, so /distribution/install=Pass|pass|PASS are all same. +# +import os +import sys +import xmlrpclib +import xml.dom.minidom +import time +from optparse import OptionParser + +# findguestrecipe +# looks up the recipeid of the guest. +# should be called from the hostmachine where the hypervisor's recipeid will be +# available. assumes that a connection to global proxy object is established +# already. +def findguestrecipe(name): + recipe_xml = proxy.get_my_recipe(dict(recipe_id=os.environ['RECIPEID'])) + doc = xml.dom.minidom.parseString(recipe_xml) + + for guestrecipe in doc.getElementsByTagName('guestrecipe'): + if guestrecipe.getAttribute('guestname') == name: + return guestrecipe.getAttribute('id') + + #if we didn't find it, return None + return None + +tasks_list = None +tasks_dict = { } +total_sleep = 0 +timeout = -1 +interval = 20 +errors = [] +parser = OptionParser() + +proxy = xmlrpclib.ServerProxy('http://%s:8000/RPC2' % os.environ['LAB_CONTROLLER']) + +# add options to parser all the options... +parser.add_option("--guestname", dest="guestname", help="Name of guest", + metavar="GUESTNAME") +parser.add_option("--guestrecipe", dest="guestrecipe", help="Recipeid of guest", + metavar="GUESTRECIPE") +parser.add_option("--timeout", dest="timeout", help="Time to wait until the \ + tasks are completed", metavar="TIMEOUT") +parser.add_option("--interval", dest="interval", help="How often to query the \ + lab controller to check on the tasks", metavar="INTERVAL") + +(options, args) = parser.parse_args() + +if not options.guestname and not options.guestrecipe: + print "no guestname or guestrecipe" + print "either one of them must be supplied" + sys.exit(1) + +if options.guestrecipe: + guestrecipe = options.guestrecipe +else: + guestrecipe = findguestrecipe(options.guestname) + if not guestrecipe: + print "Can't find recipeid for %s", (options.guestname) + sys.exit(3) + +if options.timeout: + timeout = options.timeout + if timeout[-1] == 's' or timeout[-1] == 'S': + timeout = int(timeout[:-1]) + elif timeout[-1] == 'm' or timeout[-1] == 'M': + timeout = int(timeout[:-1]) + timeout = timeout * 60 + elif timeout[-1] == 'h' or timeout[-1] == 'H': + timeout = int(timeout[:-1]) + timeout = timeout * 3600 + elif timeout[-1] == 'd' or timeout[-1] == 'D': + timeout = int(timeout[:-1]) + timeout = timeout * 86400 + timeout = int(timeout) + +if options.interval: + interval = options.interval + if interval[-1] == 's' or interval[-1] == 'S': + interval = int(interval[:-1]) + elif interval[-1] == 'm' or interval[-1] == 'M': + interval = int(interval[:-1]) + interval = interval * 60 + elif interval[-1] == 'h' or interval[-1] == 'H': + interval = int(interval[:-1]) + interval = interval * 3600 + elif interval[-1] == 'd' or interval[-1] == 'D': + interval = int(interval[:-1]) + interval = interval * 86400 + interval = int(interval) + +# if there are any args, they'll be the tasks. +tasks_list = args +# default tasks_list is all +if not tasks_list: + tasks_list = [] + # find all the tasks inside the guest + recipe_xml = proxy.get_my_recipe(dict(recipe_id=guestrecipe)) + doc = xml.dom.minidom.parseString(recipe_xml) + for xmltasks in doc.getElementsByTagName('task'): + taskname = xmltasks.getAttribute('name') + tasks_list.append(taskname) + + +print "guestrecipe that'll be used is: " + guestrecipe + +# walk thru tasks and see what (if any) results that needs to be completed +for task in tasks_list: + idv_task = task.split('=') + if len(idv_task) > 1: + tasks_dict[idv_task[0]] = idv_task[1] + else: + tasks_dict[idv_task[0]] = None + +print "waiting for below tasks to be completed in %s :" % (guestrecipe) +print "Task name Result " +for key,value in tasks_dict.iteritems(): + print "%s %s" % (key,value) + +while len(tasks_dict) > 0: + if timeout > -1: + if total_sleep > timeout: + print "Timeout waiting for the tasks to complete." + if len(errors) > 0: + print "Also these tasks didn't have the expected result: " + print errors + sys.exit(5) + + recipe_xml = proxy.get_my_recipe(dict(recipe_id=os.environ['RECIPEID'])) + doc = xml.dom.minidom.parseString(recipe_xml) + + for xmltasks in doc.getElementsByTagName('task'): + taskname = xmltasks.getAttribute('name') + taskstatus = xmltasks.getAttribute('status') + taskresult = xmltasks.getAttribute('result') + if taskstatus != 'Completed': + continue + + if taskname in tasks_dict: + # check if we need to be aware of the result + if tasks_dict[taskname] is not None: + if taskresult.lower() != tasks_dict[taskname].lower(): + print "For %s looking for %s but got %s" \ + % (taskname,tasks_dict[taskname],taskresult) + errors.append(taskname) + del tasks_dict[taskname] + else: + print "Got %s done" % (taskname) + del tasks_dict[taskname] + if len(tasks_dict) > 0: + print "Now waiting for : " + for key,value in tasks_dict.iteritems(): + print "%s %s" % (key,value) + + else: + print "Got %s done" % (taskname) + del tasks_dict[taskname] + if len(tasks_dict) > 0: + print "Now waiting for : " + for key,value in tasks_dict.iteritems(): + print "%s %s" % (key,value) + + if len(tasks_dict) == 0: + break + total_sleep = total_sleep + interval + time.sleep(interval) + +if len(errors) > 0: + print "These tasks didn't have the expected result: " + print errors + sys.exit(2) + +sys.exit(0) diff --git a/distribution/virt/import/wait4login b/distribution/virt/import/wait4login new file mode 100755 index 0000000..349d21b --- /dev/null +++ b/distribution/virt/import/wait4login @@ -0,0 +1,48 @@ +#!/usr/bin/expect + +if { $argc != 1 && $argc != 2 } { + send_user "Usage: $argv0 guestname <timeout>\n"; + send_user "timeout is optional. \n" + exit 1; +} +set timeout -1 +set guestname [lindex $argv 0] +set prompt "(%|#|\\\$) $" + +if { $argc == 2 } { + if { ![string is integer [lindex $argv 1]] } { + send_user "Timeout value must be an integer.\n"; + exit 1; + } + set timeout [lindex $argv 1] +} + +# spawn whatever the guest is configured with +if { [catch {exec isfileconsole $guestname} errStr] } { + spawn virsh console $guestname + sleep 1 + send "\r\n" + sleep 1 + send "\r\n" +} else { + if { [ catch { set console_file [exec getconsolefile $guestname] } ] } { + send_user "error with set console_file \[getconsolefile $guestname\] \n" + exit 1; + } + spawn tail -f $console_file +} +expect { + -exact "login: " { exit 0; } + timeout { + send_user "didn't get prompt in $timeout seconds \n"; + exit 1; + } + eof { + send_user "Unexpected EOF ..\n"; + exit 1; + } +} + +#should never come here ... +exit 1; + diff --git a/distribution/virt/import/wait4shutdown b/distribution/virt/import/wait4shutdown new file mode 100755 index 0000000..1a0e2d9 --- /dev/null +++ b/distribution/virt/import/wait4shutdown @@ -0,0 +1,41 @@ +#!/usr/bin/expect + +if { $argc != 1 && $argc != 2 } { + send_user "Usage: $argv0 guestname <timeout>\n"; + send_user "timeout is optional. \n" + exit 1; +} +set timeout -1 +set guestname [lindex $argv 0] +set prompt "(%|#|\\\$) $" + +if { $argc == 2 } { + if { ![string is integer [lindex $argv 1]] } { + send_user "Timeout value must be an integer.\n"; + exit 1; + } + set timeout [lindex $argv 1] +} + +# spawn whatever the guest is configured with +if { [catch {exec isfileconsole $guestname} errStr] } { + spawn virsh console $guestname +} else { + if { [ catch { set console_file [exec getconsolefile $guestname] } ] } { + send_user "error with set console_file \[getconsolefile $guestname\] \n" + exit 1; + } + spawn tail -f $console_file +} +expect { + "Power down." { exit 0; } + timeout { + send_user "didn't get prompt in $timeout seconds \n"; + exit 1; + } + eof { exit 0; } +} + +#should never come here ... +exit 1; + diff --git a/distribution/virt/import/xmltramp.py b/distribution/virt/import/xmltramp.py new file mode 100755 index 0000000..25dd0d0 --- /dev/null +++ b/distribution/virt/import/xmltramp.py @@ -0,0 +1,361 @@ +"""xmltramp: Make XML documents easily accessible.""" + +__version__ = "2.17" +__author__ = "Aaron Swartz" +__credits__ = "Many thanks to pjz, bitsko, and DanC." +__copyright__ = "(C) 2003-2006 Aaron Swartz. GNU GPL 2." + +if not hasattr(__builtins__, 'True'): True, False = 1, 0 +def isstr(f): return isinstance(f, type('')) or isinstance(f, type(u'')) +def islst(f): return isinstance(f, type(())) or isinstance(f, type([])) + +empty = {'http://www.w3.org/1999/xhtml': ['img', 'br', 'hr', 'meta', 'link', 'base', 'param', 'input', 'col', 'area']} + +def quote(x, elt=True): + if elt and '<' in x and len(x) > 24 and x.find(']]>') == -1: return "<![CDATA["+x+"]]>" + else: x = x.replace('&', '&').replace('<', '<').replace(']]>', ']]>') + if not elt: x = x.replace('"', '"') + return x + +class Element: + def __init__(self, name, attrs=None, children=None, prefixes=None): + if islst(name) and name[0] == None: name = name[1] + if attrs: + na = {} + for k in attrs.keys(): + if islst(k) and k[0] == None: na[k[1]] = attrs[k] + else: na[k] = attrs[k] + attrs = na + + self._name = name + self._attrs = attrs or {} + self._dir = children or [] + + prefixes = prefixes or {} + self._prefixes = dict(zip(prefixes.values(), prefixes.keys())) + + if prefixes: self._dNS = prefixes.get(None, None) + else: self._dNS = None + + def __repr__(self, recursive=0, multiline=0, inprefixes=None): + def qname(name, inprefixes): + if islst(name): + if inprefixes[name[0]] is not None: + return inprefixes[name[0]]+':'+name[1] + else: + return name[1] + else: + return name + + def arep(a, inprefixes, addns=1): + out = '' + + for p in self._prefixes.keys(): + if not p in inprefixes.keys(): + if addns: out += ' xmlns' + if addns and self._prefixes[p]: out += ':'+self._prefixes[p] + if addns: out += '="'+quote(p, False)+'"' + inprefixes[p] = self._prefixes[p] + + for k in a.keys(): + out += ' ' + qname(k, inprefixes)+ '="' + quote(a[k], False) + '"' + + return out + + inprefixes = inprefixes or {u'http://www.w3.org/XML/1998/namespace':'xml'} + + # need to call first to set inprefixes: + attributes = arep(self._attrs, inprefixes, recursive) + out = '<' + qname(self._name, inprefixes) + attributes + + if not self._dir and (self._name[0] in empty.keys() + and self._name[1] in empty[self._name[0]]): + out += ' />' + return out + + out += '>' + + if recursive: + content = 0 + for x in self._dir: + if isinstance(x, Element): content = 1 + + pad = '\n' + ('\t' * recursive) + for x in self._dir: + if multiline and content: out += pad + if isstr(x): out += quote(x) + elif isinstance(x, Element): + out += x.__repr__(recursive+1, multiline, inprefixes.copy()) + else: + raise TypeError, "I wasn't expecting "+`x`+"." + if multiline and content: out += '\n' + ('\t' * (recursive-1)) + else: + if self._dir: out += '...' + + out += '</'+qname(self._name, inprefixes)+'>' + + return out + + def __unicode__(self): + text = '' + for x in self._dir: + text += unicode(x) + return ' '.join(text.split()) + + def __str__(self): + return self.__unicode__().encode('utf-8') + + def __getattr__(self, n): + if n[0] == '_': raise AttributeError, "Use foo['"+n+"'] to access the child element." + if self._dNS: n = (self._dNS, n) + for x in self._dir: + if isinstance(x, Element) and x._name == n: return x + raise AttributeError, 'No child element named %s' % repr(n) + + def __hasattr__(self, n): + for x in self._dir: + if isinstance(x, Element) and x._name == n: return True + return False + + def __setattr__(self, n, v): + if n[0] == '_': self.__dict__[n] = v + else: self[n] = v + + + def __getitem__(self, n): + if isinstance(n, type(0)): # d[1] == d._dir[1] + return self._dir[n] + elif isinstance(n, slice(0).__class__): + # numerical slices + if isinstance(n.start, type(0)): return self._dir[n.start:n.stop] + + # d['foo':] == all <foo>s + n = n.start + if self._dNS and not islst(n): n = (self._dNS, n) + out = [] + for x in self._dir: + if isinstance(x, Element) and x._name == n: out.append(x) + return out + else: # d['foo'] == first <foo> + if self._dNS and not islst(n): n = (self._dNS, n) + for x in self._dir: + if isinstance(x, Element) and x._name == n: return x + raise KeyError + + def __setitem__(self, n, v): + if isinstance(n, type(0)): # d[1] + self._dir[n] = v + elif isinstance(n, slice(0).__class__): + # d['foo':] adds a new foo + n = n.start + if self._dNS and not islst(n): n = (self._dNS, n) + + nv = Element(n) + self._dir.append(nv) + + else: # d["foo"] replaces first <foo> and dels rest + if self._dNS and not islst(n): n = (self._dNS, n) + + nv = Element(n); nv._dir.append(v) + replaced = False + + todel = [] + for i in range(len(self)): + if self[i]._name == n: + if replaced: + todel.append(i) + else: + self[i] = nv + replaced = True + if not replaced: self._dir.append(nv) + for i in todel: del self[i] + + def __delitem__(self, n): + if isinstance(n, type(0)): del self._dir[n] + elif isinstance(n, slice(0).__class__): + # delete all <foo>s + n = n.start + if self._dNS and not islst(n): n = (self._dNS, n) + + for i in range(len(self)): + if self[i]._name == n: del self[i] + else: + # delete first foo + for i in range(len(self)): + if self[i]._name == n: del self[i] + break + + def __call__(self, *_pos, **_set): + if _set: + for k in _set.keys(): self._attrs[k] = _set[k] + if len(_pos) > 1: + for i in range(0, len(_pos), 2): + self._attrs[_pos[i]] = _pos[i+1] + if len(_pos) == 1 is not None: + return self._attrs[_pos[0]] + if len(_pos) == 0: + return self._attrs + + def __len__(self): return len(self._dir) + +class Namespace: + def __init__(self, uri): self.__uri = uri + def __getattr__(self, n): return (self.__uri, n) + def __getitem__(self, n): return (self.__uri, n) + +from xml.sax.handler import EntityResolver, DTDHandler, ContentHandler, ErrorHandler + +class Seeder(EntityResolver, DTDHandler, ContentHandler, ErrorHandler): + def __init__(self): + self.stack = [] + self.ch = '' + self.prefixes = {} + ContentHandler.__init__(self) + + def startPrefixMapping(self, prefix, uri): + if not self.prefixes.has_key(prefix): self.prefixes[prefix] = [] + self.prefixes[prefix].append(uri) + def endPrefixMapping(self, prefix): + self.prefixes[prefix].pop() + + def startElementNS(self, name, qname, attrs): + ch = self.ch; self.ch = '' + if ch and not ch.isspace(): self.stack[-1]._dir.append(ch) + + attrs = dict(attrs) + newprefixes = {} + for k in self.prefixes.keys(): newprefixes[k] = self.prefixes[k][-1] + + self.stack.append(Element(name, attrs, prefixes=newprefixes.copy())) + + def characters(self, ch): + self.ch += ch + + def endElementNS(self, name, qname): + ch = self.ch; self.ch = '' + if ch and not ch.isspace(): self.stack[-1]._dir.append(ch) + + element = self.stack.pop() + if self.stack: + self.stack[-1]._dir.append(element) + else: + self.result = element + +from xml.sax import make_parser +from xml.sax.handler import feature_namespaces + +def seed(fileobj): + seeder = Seeder() + parser = make_parser() + parser.setFeature(feature_namespaces, 1) + parser.setContentHandler(seeder) + parser.parse(fileobj) + return seeder.result + +def parse(text): + from StringIO import StringIO + return seed(StringIO(text)) + +def load(url): + import urllib + return seed(urllib.urlopen(url)) + +def unittest(): + parse('<doc>a<baz>f<b>o</b>ob<b>a</b>r</baz>a</doc>').__repr__(1,1) == \ + '<doc>\n\ta<baz>\n\t\tf<b>o</b>ob<b>a</b>r\n\t</baz>a\n</doc>' + + assert str(parse("<doc />")) == "" + assert str(parse("<doc>I <b>love</b> you.</doc>")) == "I love you." + assert parse("<doc>\nmom\nwow\n</doc>")[0].strip() == "mom\nwow" + assert str(parse('<bing> <bang> <bong>center</bong> </bang> </bing>')) == "center" + assert str(parse('<doc>\xcf\x80</doc>')) == '\xcf\x80' + + d = Element('foo', attrs={'foo':'bar'}, children=['hit with a', Element('bar'), Element('bar')]) + + try: + d._doesnotexist + raise "ExpectedError", "but found success. Damn." + except AttributeError: pass + assert d.bar._name == 'bar' + try: + d.doesnotexist + raise "ExpectedError", "but found success. Damn." + except AttributeError: pass + + assert hasattr(d, 'bar') == True + + assert d('foo') == 'bar' + d(silly='yes') + assert d('silly') == 'yes' + assert d() == d._attrs + + assert d[0] == 'hit with a' + d[0] = 'ice cream' + assert d[0] == 'ice cream' + del d[0] + assert d[0]._name == "bar" + assert len(d[:]) == len(d._dir) + assert len(d[1:]) == len(d._dir) - 1 + assert len(d['bar':]) == 2 + d['bar':] = 'baz' + assert len(d['bar':]) == 3 + assert d['bar']._name == 'bar' + + d = Element('foo') + + doc = Namespace("http://example.org/bar") + bbc = Namespace("http://example.org/bbc") + dc = Namespace("http://purl.org/dc/elements/1.1/") + d = parse("""<doc version="2.7182818284590451" + xmlns="http://example.org/bar" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:bbc="http://example.org/bbc"> + <author>John Polk and John Palfrey</author> + <dc:creator>John Polk</dc:creator> + <dc:creator>John Palfrey</dc:creator> + <bbc:show bbc:station="4">Buffy</bbc:show> + </doc>""") + + assert repr(d) == '<doc version="2.7182818284590451">...</doc>' + assert d.__repr__(1) == '<doc xmlns:bbc="http://example.org/bbc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://example.org/bar" version="2.7182818284590451"><author>John Polk and John Palfrey</author><dc:creator>John Polk</dc:creator><dc:creator>John Palfrey</dc:creator><bbc:show bbc:station="4">Buffy</bbc:show></doc>' + assert d.__repr__(1,1) == '<doc xmlns:bbc="http://example.org/bbc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://example.org/bar" version="2.7182818284590451">\n\t<author>John Polk and John Palfrey</author>\n\t<dc:creator>John Polk</dc:creator>\n\t<dc:creator>John Palfrey</dc:creator>\n\t<bbc:show bbc:station="4">Buffy</bbc:show>\n</doc>' + + assert repr(parse("<doc xml:lang='en' />")) == '<doc xml:lang="en"></doc>' + + assert str(d.author) == str(d['author']) == "John Polk and John Palfrey" + assert d.author._name == doc.author + assert str(d[dc.creator]) == "John Polk" + assert d[dc.creator]._name == dc.creator + assert str(d[dc.creator:][1]) == "John Palfrey" + d[dc.creator] = "Me!!!" + assert str(d[dc.creator]) == "Me!!!" + assert len(d[dc.creator:]) == 1 + d[dc.creator:] = "You!!!" + assert len(d[dc.creator:]) == 2 + + assert d[bbc.show](bbc.station) == "4" + d[bbc.show](bbc.station, "5") + assert d[bbc.show](bbc.station) == "5" + + e = Element('e') + e.c = '<img src="foo">' + assert e.__repr__(1) == '<e><c><img src="foo"></c></e>' + e.c = '2 > 4' + assert e.__repr__(1) == '<e><c>2 > 4</c></e>' + e.c = 'CDATA sections are <em>closed</em> with ]]>.' + assert e.__repr__(1) == '<e><c>CDATA sections are <em>closed</em> with ]]>.</c></e>' + e.c = parse('<div xmlns="http://www.w3.org/1999/xhtml">i<br /><span></span>love<br />you</div>') + assert e.__repr__(1) == '<e><c><div xmlns="http://www.w3.org/1999/xhtml">i<br /><span></span>love<br />you</div></c></e>' + + e = Element('e') + e('c', 'that "sucks"') + assert e.__repr__(1) == '<e c="that "sucks""></e>' + + + assert quote("]]>") == "]]>" + assert quote('< dkdkdsd dkd sksdksdfsd fsdfdsf]]> kfdfkg >') == '< dkdkdsd dkd sksdksdfsd fsdfdsf]]> kfdfkg >' + + assert parse('<x a="<"></x>').__repr__(1) == '<x a="<"></x>' + assert parse('<a xmlns="http://a"><b xmlns="http://b"/></a>').__repr__(1) == '<a xmlns="http://a"><b xmlns="http://b"></b></a>' + +if __name__ == '__main__': unittest() diff --git a/distribution/virt/import/xmstartguest b/distribution/virt/import/xmstartguest new file mode 100755 index 0000000..a982435 --- /dev/null +++ b/distribution/virt/import/xmstartguest @@ -0,0 +1,61 @@ +#!/usr/bin/expect +# +# utility script to start the guest and make sure that it started is up +# it has a default of 5 minutes to wait for the guest to start up. +# + +if { $argc < 1 } { + send_user "Usage: $argv0 guestname <optional timeout value>" + exit 1 +} + +# stupid hack for when this is called from a script +if { $argc == 1 } { + set argv_tmp [lindex $argv 0] + set argv [split $argv_tmp] +} + +if { [llength $argv] > 1 } { + set waittimeout [lindex $argv 1] +} else { + set waittimeout 300 +} + +set guest [lindex $argv 0] +set prompt "(%|#|\\\$) $" +log_user 0 + +spawn xm create $guest +set start_spawn $spawn_id + +expect { + -i $start_spawn "Domain $guest started" { exp_continue; } + -i $start_spawn timeout { + send_user "timeout on xm start $guest\n"; + exit 1; + } + -i $start_spawn eof { } +} + +spawn xm console $guest +set con_spawn $spawn_id +set timeout $waittimeout + +send -i $con_spawn "\r" +send -i $con_spawn "\r" +send -i $con_spawn "\r" +expect { + -i $con_spawn "login: " { send -i $con_spawn ; exit 0; } + -i $con_spawn -re "(P|p)assword:" { send -i $con_spawn ; exit 0; } + -i $con_spawn -re $prompt { send -i $con_spawn ; exit 0; } + -i $con_spawn timeout { + send_user "start timeout\n"; + exit 1; + } + -i $con_spawn eof { + send_user "start EOF\n"; + exit 1; + } +} + +exit 1; diff --git a/distribution/virt/import/xmstopguest b/distribution/virt/import/xmstopguest new file mode 100755 index 0000000..cd1730a --- /dev/null +++ b/distribution/virt/import/xmstopguest @@ -0,0 +1,52 @@ +#!/usr/bin/expect +# +# utility script to stop the guest and make sure that it has shutdown +# it has a default of 5 minutes to wait for the guest to shut +# + +if { $argc < 1 } { + send_user "Usage: $argv0 guestname <optional timeout value>" + exit 1 +} + +# stupid hack for when this is called from a script +if { $argc == 1 } { + set argv_tmp [lindex $argv 0] + set argv [split $argv_tmp] +} + +if { [llength $argv] > 1 } { + set waittimeout [lindex $argv 1] +} else { + set waittimeout 300 +} + +set guest [lindex $argv 0] +set prompt "(%|#|\\\$) $" +log_user 0 + +spawn xm shutdown $guest +set stop_spawn $spawn_id + +expect { + -i $stop_spawn timeout { + send_user "timeout on xm shutdown $guest\n"; + exit 1; + } + -i $stop_spawn eof { } +} + +spawn xm console $guest +set con_spawn $spawn_id +set timeout $waittimeout + +expect { + -i $con_spawn "System halted." { exit 0; } + -i $con_spawn timeout { + send_user "stop timeout\n"; + exit 1; + } + -i $con_spawn eof { exit 0; } +} + +exit 1; diff --git a/distribution/virt/import/zrhel5_write_consolelogs.initd b/distribution/virt/import/zrhel5_write_consolelogs.initd new file mode 100755 index 0000000..1ec2c8e --- /dev/null +++ b/distribution/virt/import/zrhel5_write_consolelogs.initd @@ -0,0 +1,111 @@ +#!/bin/sh +# +# zrhel5_write_consolelogs Agent for reporting virtual guest IDs to subscription-manager +# +# chkconfig: 2345 98 99 +# description: Agent for reporting virtual guest IDs to subscription-manager + +### BEGIN INIT INFO +# Provides: zrhel5_write_consolelogs +# Required-Start: $network libvirtd +# Required-Stop: +# Should-Start: +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: start and stop zrhel5_write_consolelogs +# Description: Agent for reporting virtual guest IDs to subscription-manager +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +exec="/usr/local/bin/zrhel5_write_consolelogs" +prog="zrhel5_write_consolelogs" +#config="/usr/local/etc/zrhel5_write_consolelogs.conf" +pidfile="/var/run/$prog.pid" +args="" + +# Export all variables in /etc/sysconfig/zrhel5_write_consolelogs +set -a +[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog +# below is where we'll get the LAB_CONTROLLER +[ -e /etc/profile.d/rh-env.sh ] && . /etc/profile.d/rh-env.sh +set +a + +lockfile=/var/lock/subsys/zrhel5_write_consolelogs + +start() { + [ -x $exec ] || exit 5 + echo -n $"Starting $prog: " + #daemon --pidfile $pidfile nohup $exec & + nohup $exec $args & + childpid=$! + if kill -0 ${childpid}; then + echo + touch $lockfile + echo "${childpid}" > ${pidfile} + return 0 + else + return -1 + fi +} + +stop() { + echo -n $"Stopping $prog: " + killproc -p $pidfile $prog + retval=$? + echo + [ $retval -eq 0 ] && rm -f $lockfile + return $retval +} + +restart() { + stop + start +} + +force_reload() { + restart +} + +rh_status() { + status -p $pidfile $prog +} + +rh_status_q() { + rh_status >/dev/null 2>&1 +} + + +case "$1" in + start) + rh_status_q && exit 0 + $1 + ;; + stop) + rh_status_q || exit 0 + $1 + ;; + restart) + $1 + ;; + reload) + rh_status_q || exit 7 + $1 + ;; + force-reload) + force_reload + ;; + status) + rh_status + ;; + condrestart|try-restart) + rh_status_q || exit 0 + restart + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" + exit 2 +esac +exit $? diff --git a/distribution/virt/import/zrhel5_write_consolelogs.py b/distribution/virt/import/zrhel5_write_consolelogs.py new file mode 100755 index 0000000..6d069f5 --- /dev/null +++ b/distribution/virt/import/zrhel5_write_consolelogs.py @@ -0,0 +1,537 @@ +#!/usr/bin/python -u +# +# +# +################################################################################# +# Start off by implementing a general purpose event loop for anyones use +################################################################################# + +import sys +import getopt +import os +import libvirt +import select +import errno +import time +import threading +import subprocess +import signal +import pty +import fcntl +from xml.dom import minidom +from optparse import OptionParser + +debugstr = 0 + +# +# This general purpose event loop will support waiting for file handle +# I/O and errors events, as well as scheduling repeatable timers with +# a fixed interval. +# +# It is a pure python implementation based around the poll() API +# +class virEventLoopPure: + # This class contains the data we need to track for a + # single file handle + class virEventLoopPureHandle: + def __init__(self, handle, fd, events, cb, opaque): + self.handle = handle + self.fd = fd + self.events = events + self.cb = cb + self.opaque = opaque + + def get_id(self): + return self.handle + + def get_fd(self): + return self.fd + + def get_events(self): + return self.events + + def set_events(self, events): + self.events = events + + def dispatch(self, events): + self.cb(self.handle, + self.fd, + events, + self.opaque[0], + self.opaque[1]) + + # This class contains the data we need to track for a + # single periodic timer + class virEventLoopPureTimer: + def __init__(self, timer, interval, cb, opaque): + self.timer = timer + self.interval = interval + self.cb = cb + self.opaque = opaque + self.lastfired = 0 + + def get_id(self): + return self.timer + + def get_interval(self): + return self.interval + + def set_interval(self, interval): + self.interval = interval + + def get_last_fired(self): + return self.lastfired + + def set_last_fired(self, now): + self.lastfired = now + + def dispatch(self): + self.cb(self.timer, + self.opaque[0], + self.opaque[1]) + + + def __init__(self, debug=False): + self.debugOn = debug + self.poll = select.poll() + self.pipetrick = os.pipe() + self.nextHandleID = 1 + self.nextTimerID = 1 + self.handles = [] + self.timers = [] + self.quit = False + + # The event loop can be used from multiple threads at once. + # Specifically while the main thread is sleeping in poll() + # waiting for events to occur, another thread may come along + # and add/update/remove a file handle, or timer. When this + # happens we need to interrupt the poll() sleep in the other + # thread, so that it'll see the file handle / timer changes. + # + # Using OS level signals for this is very unreliable and + # hard to implement correctly. Thus we use the real classic + # "self pipe" trick. A anonymous pipe, with one end registered + # with the event loop for input events. When we need to force + # the main thread out of a poll() sleep, we simple write a + # single byte of data to the other end of the pipe. + self.debug("Self pipe watch %d write %d" %(self.pipetrick[0], self.pipetrick[1])) + self.poll.register(self.pipetrick[0], select.POLLIN) + + def debug(self, msg): + if self.debugOn: + print msg + + + # Calculate when the next timeout is due to occurr, returning + # the absolute timestamp for the next timeout, or 0 if there is + # no timeout due + def next_timeout(self): + next = 0 + for t in self.timers: + last = t.get_last_fired() + interval = t.get_interval() + if interval < 0: + continue + if next == 0 or (last + interval) < next: + next = last + interval + + return next + + # Lookup a virEventLoopPureHandle object based on file descriptor + def get_handle_by_fd(self, fd): + for h in self.handles: + if h.get_fd() == fd: + return h + return None + + # Lookup a virEventLoopPureHandle object based on its event loop ID + def get_handle_by_id(self, handleID): + for h in self.handles: + if h.get_id() == handleID: + return h + return None + + + # This is the heart of the event loop, performing one single + # iteration. It asks when the next timeout is due, and then + # calcuates the maximum amount of time it is able to sleep + # for in poll() pending file handle events. + # + # It then goes into the poll() sleep. + # + # When poll() returns, there will zero or more file handle + # events which need to be dispatched to registered callbacks + # It may also be time to fire some periodic timers. + # + # Due to the coarse granularity of schedular timeslices, if + # we ask for a sleep of 500ms in order to satisfy a timer, we + # may return upto 1 schedular timeslice early. So even though + # our sleep timeout was reached, the registered timer may not + # technically be at its expiry point. This leads to us going + # back around the loop with a crazy 5ms sleep. So when checking + # if timeouts are due, we allow a margin of 20ms, to avoid + # these pointless repeated tiny sleeps. + def run_once(self): + sleep = -1 + next = self.next_timeout() + self.debug("Next timeout due at %d" % next) + if next > 0: + now = int(time.time() * 1000) + if now >= next: + sleep = 0 + else: + sleep = (next - now) / 1000.0 + + self.debug("Poll with a sleep of %d" % sleep) + events = self.poll.poll(sleep) + + # Dispatch any file handle events that occurred + for (fd, revents) in events: + # See if the events was from the self-pipe + # telling us to wakup. if so, then discard + # the data just continue + if fd == self.pipetrick[0]: + data = os.read(fd, 1) + continue + + h = self.get_handle_by_fd(fd) + if h: + self.debug("Dispatch fd %d handle %d events %d" % (fd, h.get_id(), revents)) + h.dispatch(self.events_from_poll(revents)) + + now = int(time.time() * 1000) + for t in self.timers: + interval = t.get_interval() + if interval < 0: + continue + + want = t.get_last_fired() + interval + # Deduct 20ms, since schedular timeslice + # means we could be ever so slightly early + if now >= (want-20): + self.debug("Dispatch timer %d now %s want %s" % (t.get_id(), str(now), str(want))) + t.set_last_fired(now) + t.dispatch() + + + # Actually the event loop forever + def run_loop(self): + self.quit = False + while not self.quit: + self.run_once() + + def interrupt(self): + os.write(self.pipetrick[1], 'c') + + + # Registers a new file handle 'fd', monitoring for 'events' (libvirt + # event constants), firing the callback cb() when an event occurs. + # Returns a unique integer identier for this handle, that should be + # used to later update/remove it + def add_handle(self, fd, events, cb, opaque): + handleID = self.nextHandleID + 1 + self.nextHandleID = self.nextHandleID + 1 + + h = self.virEventLoopPureHandle(handleID, fd, events, cb, opaque) + self.handles.append(h) + + self.poll.register(fd, self.events_to_poll(events)) + self.interrupt() + + self.debug("Add handle %d fd %d events %d" % (handleID, fd, events)) + + return handleID + + # Registers a new timer with periodic expiry at 'interval' ms, + # firing cb() each time the timer expires. If 'interval' is -1, + # then the timer is registered, but not enabled + # Returns a unique integer identier for this handle, that should be + # used to later update/remove it + def add_timer(self, interval, cb, opaque): + timerID = self.nextTimerID + 1 + self.nextTimerID = self.nextTimerID + 1 + + h = self.virEventLoopPureTimer(timerID, interval, cb, opaque) + self.timers.append(h) + self.interrupt() + + self.debug("Add timer %d interval %d" % (timerID, interval)) + + return timerID + + # Change the set of events to be monitored on the file handle + def update_handle(self, handleID, events): + h = self.get_handle_by_id(handleID) + if h: + h.set_events(events) + self.poll.unregister(h.get_fd()) + self.poll.register(h.get_fd(), self.events_to_poll(events)) + self.interrupt() + + self.debug("Update handle %d fd %d events %d" % (handleID, h.get_fd(), events)) + + # Change the periodic frequency of the timer + def update_timer(self, timerID, interval): + for h in self.timers: + if h.get_id() == timerID: + h.set_interval(interval); + self.interrupt() + + self.debug("Update timer %d interval %d" % (timerID, interval)) + break + + # Stop monitoring for events on the file handle + def remove_handle(self, handleID): + handles = [] + for h in self.handles: + if h.get_id() == handleID: + self.poll.unregister(h.get_fd()) + self.debug("Remove handle %d fd %d" % (handleID, h.get_fd())) + else: + handles.append(h) + self.handles = handles + self.interrupt() + + # Stop firing the periodic timer + def remove_timer(self, timerID): + timers = [] + for h in self.timers: + if h.get_id() != timerID: + timers.append(h) + self.debug("Remove timer %d" % timerID) + self.timers = timers + self.interrupt() + + # Convert from libvirt event constants, to poll() events constants + def events_to_poll(self, events): + ret = 0 + if events & libvirt.VIR_EVENT_HANDLE_READABLE: + ret |= select.POLLIN + if events & libvirt.VIR_EVENT_HANDLE_WRITABLE: + ret |= select.POLLOUT + if events & libvirt.VIR_EVENT_HANDLE_ERROR: + ret |= select.POLLERR; + if events & libvirt.VIR_EVENT_HANDLE_HANGUP: + ret |= select.POLLHUP; + return ret + + # Convert from poll() event constants, to libvirt events constants + def events_from_poll(self, events): + ret = 0; + if events & select.POLLIN: + ret |= libvirt.VIR_EVENT_HANDLE_READABLE; + if events & select.POLLOUT: + ret |= libvirt.VIR_EVENT_HANDLE_WRITABLE; + if events & select.POLLNVAL: + ret |= libvirt.VIR_EVENT_HANDLE_ERROR; + if events & select.POLLERR: + ret |= libvirt.VIR_EVENT_HANDLE_ERROR; + if events & select.POLLHUP: + ret |= libvirt.VIR_EVENT_HANDLE_HANGUP; + return ret; + + +########################################################################### +# Now glue an instance of the general event loop into libvirt's event loop +########################################################################### + +# This single global instance of the event loop wil be used for +# monitoring libvirt events +eventLoop = virEventLoopPure(debug=False) + +# This keeps track of what thread is running the event loop, +# (if it is run in a background thread) +eventLoopThread = None + + +# These next set of 6 methods are the glue between the official +# libvirt events API, and our particular impl of the event loop +# +# There is no reason why the 'virEventLoopPure' has to be used. +# An application could easily may these 6 glue methods hook into +# another event loop such as GLib's, or something like the python +# Twisted event framework. + +def virEventAddHandleImpl(fd, events, cb, opaque): + global eventLoop + return eventLoop.add_handle(fd, events, cb, opaque) + +def virEventUpdateHandleImpl(handleID, events): + global eventLoop + return eventLoop.update_handle(handleID, events) + +def virEventRemoveHandleImpl(handleID): + global eventLoop + return eventLoop.remove_handle(handleID) + +def virEventAddTimerImpl(interval, cb, opaque): + global eventLoop + return eventLoop.add_timer(interval, cb, opaque) + +def virEventUpdateTimerImpl(timerID, interval): + global eventLoop + return eventLoop.update_timer(timerID, interval) + +def virEventRemoveTimerImpl(timerID): + global eventLoop + return eventLoop.remove_timer(timerID) + +# This tells libvirt what event loop implementation it +# should use +def virEventLoopPureRegister(): + libvirt.virEventRegisterImpl(virEventAddHandleImpl, + virEventUpdateHandleImpl, + virEventRemoveHandleImpl, + virEventAddTimerImpl, + virEventUpdateTimerImpl, + virEventRemoveTimerImpl) + +# Directly run the event loop in the current thread +def virEventLoopPureRun(): + global eventLoop + eventLoop.run_loop() + +# Spawn a background thread to run the event loop +def virEventLoopPureStart(): + global eventLoopThread + virEventLoopPureRegister() + eventLoopThread = threading.Thread(target=virEventLoopPureRun, name="libvirtEventLoop") + eventLoopThread.setDaemon(True) + eventLoopThread.start() + + +########################################################################## +# Everything that now follows is a simple demo of domain lifecycle events +########################################################################## +def eventToString(event): + eventStrings = ( "Defined", + "Undefined", + "Started", + "Suspended", + "Resumed", + "Stopped" ); + return eventStrings[event]; + +def detailToString(event, detail): + eventStrings = ( + ( "Added", "Updated" ), + ( "Removed" ), + ( "Booted", "Migrated", "Restored", "Snapshot" ), + ( "Paused", "Migrated", "IOError", "Watchdog" ), + ( "Unpaused", "Migrated"), + ( "Shutdown", "Destroyed", "Crashed", "Migrated", "Saved", "Failed", "Snapshot") + ) + return eventStrings[event][detail] + +def readconsoleandsave (domainname): + global testdir + filename = testdir+"/guests/"+domainname+"/logs/"+domainname+"_console.log" + args = ["/usr/bin/virsh"] + args = args + [ "console", "%s" % domainname] + console_fd = open(filename, "a+") + console_fid = console_fd.fileno() + fds[domainname] = console_fd + (child, fd) = pty.fork() + if child: + try: + flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) + except Exception, e: + print e + debugprint("Forked %s %s" % (args[0], args)) + else: + os.dup2(console_fid, 1) + time.sleep(1) + os.execvp(args[0], args) + os._exit(1) + + #print "child is : %d " % child + return child + +def myDomainEventCallback1 (conn, dom, event, detail, opaque): + #print "name: %s event: %s " % ( dom.name(), eventToString(event) ) + global pids + if eventToString(event) == "Started": + debugprint("%s started, will call readconsoleandsave" % dom.name()) + pids[dom.name()] = readconsoleandsave(dom.name()) + #print "pids[%s] is %s " % (dom.name(), pids[dom.name()]) + elif eventToString(event) == "Stopped": + if dom.name() in fds: + fds[dom.name()].close() + if dom.name() in pids: + os.kill(pids[dom.name()],signal.SIGKILL) + +def usage(): + print "usage: "+os.path.basename(sys.argv[0])+" [uri]" + print " uri will default to qemu:///system" + +def debugprint(str): + global debugstr + if (debugstr): + print "%s" % str + + +def main(): + global testdir + #try: + # opts, args = getopt.getopt(sys.argv[1:], "h", ["help"] ) + #except getopt.GetoptError, err: + # # print help information and exit: + # print str(err) # will print something like "option -a not recognized" + # usage() + # sys.exit(2) + parser = OptionParser(conflict_handler="resolve") + parser.add_option("-t" ,"--testdir", dest="testdir", help="Root dir for logs", + default="/mnt/tests/distribution/virt/install", metavar="PATH") + parser.add_option("-h" ,"--help", dest="help", help="Display help", metavar="HELP") + parser.add_option("-d" ,"--debug", dest="debug", help="Print debug", + action="store_false", default=False, metavar="DEBUG") + (options, args) = parser.parse_args() + + if options.help: + usage() + sys.exit(0) + + if options.debug: + debugstr = 1 + + if options.testdir: + testdir = options.testdir + + if len(args) > 0: + uri = args[0] + else: + #uri = "qemu:///system" + uri = subprocess.Popen(["virsh", "uri"], stdout=subprocess.PIPE).communicate()[0].strip() + + print "Using uri: " + uri + + # Run a background thread with the event loop + virEventLoopPureStart() + + vc = libvirt.open(uri) + + # Close connection on exit (to test cleanup paths) + old_exitfunc = getattr(sys, 'exitfunc', None) + def exit(): + print "Closing " + str(vc) + vc.close() + if (old_exitfunc): old_exitfunc() + sys.exitfunc = exit + + #Add 2 callbacks to prove this works with more than just one + vc.domainEventRegister(myDomainEventCallback1,None) + + # The rest of your app would go here normally, but for sake + # of demo we'll just go to sleep. The other option is to + # run the event loop in your main thread if your app is + # totally event based. + while 1: + time.sleep(1) + + +if __name__ == "__main__": + pids = { } + fds = { } + path = "" + main() |