#!/bin/sh
#
# Master start/stop script for Xen-based virtual networks
# Written by Birger Toedtmann <btoedtmann@iem.uni-due.de> in May 2005
#
# What it does: 
#  * Convert an XML network description file into Xen config files
#    for all nodes within the network description
#  * Set up 'copy-on-write' root filesystems for each node by using
#    the device-mapper mechanism inside linux
#  * Start/stop the virtual network
#
# Required: 
#   - dom0 must be Linux
#   - xen-3.0-devel: starting large networks won't work with -stable
#     because neither NR_IRQS in dom0 nor COMMAND_LINE_SIZE in domU's
#     are dimensioned accordingly there
#   - device-mapper support in dom0 to create snapshots of the domU 
#     root filesystem ('copy-on-write' mechanism in VMware and UML speak)
#   - ipcalc, libxslt, bridge-utils, autobrctl script 
#   - domU's need a script that will parse the cmd line and set up
#     the network devices specified there (look at the 'extra' config
#     within the Xen configuration files)
#
# Security warning:
#   - Don't set the $tmpdir variable to something ordinary users are
#     allowed to write to
#   - There's no sanity check on the contents of the XML network 
#     specification.  As a result, improper settings there (like 
#     a node name of "../../../etc/passwd") may result in severe
#     damage
#
# Debug hint:
#   In case of problems related to this scipt, don't start it directly
#   but make an eval without params like this: '. xvn'.  It will print a 
#   notice on missing params, however, afterwards, the subroutines
#   'createvn', 'destroyvn' etc. will be available (try 'set' to see
#   them) and might be executed step by step to nail down the cause
#   of the problem.
#




# global variables
vnconfig=vnconfig.xml
xendir=/etc/xen
tmpdir=$xendir/tmp.xvn


# Some checks and preparations
if [ ! -e $vnconfig ]; then
        echo "Could not find configuration file $vnconfig, exiting."
	exit 1
fi
		
mkdir -p $tmpdir
if [ ! -d $tmpdir ]; then
	echo "$tmpdir does not exist, exiting."
	exit 1
fi


# setupvn subroutine will configure the device-mapper
setupvn() {
	echo -n "Setting up node state files..."
	# go and configure device-mapper snapshots of the root fs
	losetup /dev/loop$xvn_loopdevstart $xvn_rootfs
	bsize=`blockdev --getsize /dev/loop$xvn_loopdevstart`
	echo "0 $bsize linear /dev/loop$xvn_loopdevstart 0" | dmsetup create ${xvn_mapperprefix}_base
	loopnum=$xvn_loopdevstart
	for i in $tmpdir/*; do
		d=`basename $i`
		loopnum=$[loopnum+1]
		dd if=/dev/zero of=$xvn_cowprefix.$d bs=1M count=$xvn_cowsize >/dev/null 2>&1
		losetup /dev/loop$loopnum $xvn_cowprefix.$d
		echo "0 $bsize snapshot /dev/mapper/${xvn_mapperprefix}_base /dev/loop$loopnum p 8" | dmsetup create $xvn_mapperprefix.$d
	done
	echo " done."
}


# tearvn will free the device-mapper and the loop devices
tearvn() {
	echo -n "Removing node state files..."
	dmsetup remove_all
	loopnum=$xvn_loopdevstart
	for i in $tmpdir/*; do
        	d=`basename $i`
        	loopnum=$[loopnum+1]
        	losetup -d /dev/loop$loopnum
        	rm $xvn_cowprefix.$d
	done
	losetup -d /dev/loop$xvn_loopdevstart
	echo " done."
}


# do it: start all nodes
startvn() {
	echo "Starting domains:"
        for i in $tmpdir/*; do
		d=`basename $i`
		cp $tmpdir/$d $xendir
		echo "  $d"
		xm create $d &>/dev/null
        done
	echo "done."
}

# stop all nodes
stopvn() {
	echo "Stopping domains:"
	for i in $tmpdir/*; do
		d=`basename $i`
		echo "  $d"
		cp $tmpdir/$d $xendir
                xm destroy $d
		rm $xendir/$d
        done
	echo "done."
}



parse_global() {
	defaultxsl=_parse_defaultconf.xsl
	echo -n "Creating XSL default config parser file..."
	cat >$tmpdir/$defaultxsl<<EOT
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" media-type="text/ascii"/>  
<xsl:template match="network">
xvn_kernel="<xsl:value-of select="defaults/kernel"/>"
xvn_rootfs="<xsl:value-of select="defaults/rootfs"/>"
xvn_memory="<xsl:value-of select="defaults/mem"/>"
xvn_cowprefix="<xsl:value-of select="defaults/cowprefix"/>"
xvn_cowsize="<xsl:value-of select="defaults/cowsize"/>"
xvn_loopdevstart="<xsl:value-of select="defaults/loopdevstart"/>"
xvn_mapperprefix="<xsl:value-of select="defaults/mapperprefix"/>"
</xsl:template>
</xsl:stylesheet>
EOT
echo " done."
echo -n "Parsing default settings in $vnconfig..."
eval `xsltproc $tmpdir/$defaultxsl $vnconfig`
rm $tmpdir/$defaultxsl
echo " done."
}

parse_network() {
	netconfxsl=_parse_netconf.xsl
	echo -n "Creating XSL network config parser file..."
	cat >$tmpdir/$netconfxsl<<EOT
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" media-type="text/plain"/>  
<xsl:template match="network">
<xsl:variable name="kernel" select="defaults/kernel"/>
<xsl:variable name="rootfs" select="defaults/rootfs"/>
<xsl:variable name="mem" select="defaults/mem"/>
<xsl:variable name="cowprefix" select="defaults/cowprefix"/>
<xsl:variable name="cowsize" select="defaults/cowsize"/>
<xsl:variable name="mapperprefix" select="defaults/mapperprefix"/>
<xsl:variable name="loopdevstart" select="defaults/loopdevstart"/>
<xsl:for-each select="node">
name = "<xsl:value-of select="name"/>"
kernel = "<xsl:value-of select="\$kernel"/>"
memory = <xsl:value-of select="\$mem"/>
disk = [ 'file:///dev/mapper/<xsl:value-of select="\$mapperprefix"/>.<xsl:value-of select="name"/>,sda1,w' ]
hostname = "<xsl:value-of select="name"/>"
root = "/dev/sda1 ro"
extra = "hostname=<xsl:value-of select="name"/> snmp=yes net=<xsl:for-each select="interface">eth<xsl:number count="interface" value="position() - 1"/>=<xsl:value-of select="ipaddr"/>,<xsl:value-of select="dummy"/></xsl:for-each>"
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
EOT
echo " done."

echo -n "Parsing network description in $vnconfig..."
xsltproc $tmpdir/$netconfxsl $vnconfig | while read x; do
        n=`if (echo $x | grep -E "^name = ">/dev/null); then p=$(echo $x | awk --field-separator='"' '{print $2}'); > $tmpdir/$p; echo $p; else echo $m; fi`;
        m=$n;
        if [ ! a$m = "a" ]; then
                if (echo $x | grep eth0= >/dev/null); then
                        nics=`echo $x | sed -e 's/=/\n/g' | grep -c eth`
                        echo "nics = $nics"
                        echo -n "vif = [ "
                        ifs=`echo $x | awk --field-separator="net=" '{print $2}'`
                        echo $ifs | sed -e 's/eth//g' -e 's/=/ /g' -e 's/,/\n/g' | while read inum ipaddr ; do
                                net=`ipcalc $ipaddr | sed -e 's/\// /' | grep -E "^Network:" | awk '{ print $2 }'`
                                if [ ! a$net = "a" ]; then
                                        echo -n "'bridge=$net', "
                                fi
                        done
                        echo "]"
                fi >> $tmpdir/$m
		echo $x >> $tmpdir/$m
        fi
done
rm $tmpdir/$netconfxsl

echo -n " done: wrote config files for nodes"
for i in $tmpdir/*; do
	d=`basename $i`
	echo -n " $d"
done
echo "."
}


case "$1" in
  start)
  	# danger: be careful that $tmpdir is set to a decent value, otherwise
  	# this will remove important files...
  	rm -f $tmpdir/*
  
        parse_global
	parse_network
	setupvn
	startvn
        ;;
  stop)
	parse_global
	stopvn
	tearvn
        ;;
  *)
        echo $"Usage: $0 {start|stop}"
esac

