#!/bin/bash

#####################################################
#	
#     Clone Xen VM from another Xen VM
#	- Version : 07/12/2009
#	- This script generate a clone of a VM and define it in Xen to be fully usable, it use Virsh command and ManipXml ("homemade" Xml parser using libXml2)
#	
#####################################################


usage() {
cat << EOF
usage: $0 options <vm label> 

OPTIONS:
  --src vm source name
  --name Cloned VM name

  --Force Force "hot Cloning" BE CAREFUL !!!
  --uuid Cloned VM uuid
  --mac Cloned Mac Adress (ex : 00:16:36:xx:xx:xx)
  
  --net Iface / mode / ip / netmask / gateway
  iface : ethX
  mode : dhcp or static
  
  --hostname new Hostname of the VM
  --autostart Autostart the VM after cloning

--src and --name must be specified !

		-Exemple : cloneVM --src RHEL5.3 --name RHEL5.3-Clone --net eth0/static/192.168.0.2/255.255.255.0/192.168.0.254 --hostname RHELcloned1

EOF
}


stockVar() {
iface=`echo $tempVar | cut -d '/' -f 1 | cut -b 1,2,3`
if [[ "$iface" = "eth" ]]
then
	mode=`echo $tempVar | cut -d '/' -f 2`
	if [[ $mode = "dhcp" ]] || [[ $mode = "static" ]]
	then
		if [[ $mode = "static" ]]
		then
			tempIp=`echo $tempVar | cut -d '/' -f 3`
			tempMask=`echo $tempVar | cut -d '/' -f 4`
			tempGate=`echo $tempVar | cut -d '/' -f 5`
			if [[ -z $tempIp ]] || [[ -z $tempMask ]] || [[ -z $tempGate ]]
			then
				echo "Error : if one network parameter is specified, others must be specified too (Ip / Mask / Gateway)"  
				exit 1
			else
				infoTab[$indiceTab]=$tempVar
				indiceTab=`expr $indiceTab + 1`
			fi
		elif [[ $mode = "dhcp" ]]
		then
			infoTab[$indiceTab]=$tempVar
			indiceTab=`expr $indiceTab + 1`
		fi
	else
		echo "Error : Mode must static or dhcp"
		exit 1
	fi
else
	echo "Error : First parameter must be eth-something"
	exit 1
fi
}

doManip() {
if [[ "$searchingForETCflag" = "0" ]] && [[ -d /tmp/$$-dir/etc ]]
then
	searchingForETCflag=1
        #Redhat#
	if [ -f /tmp/$$-dir/etc/sysconfig/network ]
        then
		echo "Red Hat Style distribution spotted ! Editing Configuration"

                #Edit Hostname
                if [ -n $newHostname  ]
                then
                         OldHostname=`cat /tmp/$$-dir/etc/sysconfig/network | grep HOSTNAME | cut -d '=' -f 2`
                         sed -i 's/HOSTNAME='$OldHostname'/HOSTNAME='$newHostname'/g' /tmp/$$-dir/etc/sysconfig/network
		fi
				
		#a bit dirty... only a bit#
		OldMac=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-eth0 | grep HWADDR | cut -d '=' -f 2`
		sed -i 's/'$OldMac'/'$macAddressTab'/g' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-eth0
		rm -rf /tmp/$$-dir/etc/rc.d/rc5.d/S05kudzu
			
				
                 eval t=\($infoTab\)
		for elt in "${t[@]}"
        	do
										
			interface=`echo $elt | cut -d '/' -f 1`
			mode=`echo $elt | cut -d '/' -f 2`
			ip=`echo $elt | cut -d '/' -f 3`
			netmask=`echo $elt | cut -d '/' -f 4`
			gateway=`echo $elt | cut -d '/' -f 5`

			bootproto=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep BOOTPROTO | cut -d "=" -f '2'`
													
					
			if [[ "$mode" = "dhcp" ]] && [[ "$bootproto" = "none" ]]
			then
				sed -i 's/BOOTPROTO=none/BOOTPROTO=dhcp/g' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
				ligne=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep -n IPADDR | cut -d ':' -f 1`
				sed -i ''$ligne'd' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
				ligne=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep -n NETMASK | cut -d ':' -f 1`
				sed -i ''$ligne'd' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
				ligne=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep -n GATEWAY | cut -d ':' -f 1`
				sed -i ''$ligne'd' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
			else
				if [[ "$bootproto" = "none" ]] &&  [[ "$mode" = "static" ]]
				then
					OldIP=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep IPADDR | cut -d "=" -f '2'`				
					sed -i 's/'$OldIP'/'$ip'/g' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
					OldMask=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep NETMASK | cut -d "=" -f '2'`
					sed -i 's/'$OldMask'/'$netmask'/g' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
					OldGateway=`cat /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface | grep GATEWAY | cut -d "=" -f '2'`
					sed -i 's/'$OldGateway'/'$gateway'/g' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
				elif [[ "$bootproto" = "dhcp" ]] &&  [[ "$mode" = "static" ]]
				then
					sed -i 's/BOOTPROTO=dhcp/BOOTPROTO=none/g' /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
					echo IPADDR=$ip >> /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
					echo NETMASK=$netmask >> /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
					echo GATEWAY=$gateway >> /tmp/$$-dir/etc/sysconfig/network-scripts/ifcfg-$interface
				fi
			fi
		done
		#Debian
	elif [ -f /tmp/$$-dir/etc/network/interfaces ]
	then
		echo "Debian Style distribution spotted ! Editing Configuration"
		rm -rf /tmp/$$-dir/etc/udev/rules.d/70-persistent-net.rules
                                
		#Edit Hostname
                if [ -n $newHostname  ]
		then
			rm -rf /tmp/$$-dir/etc/hostname
			echo $newHostname >> /tmp/$$-dir/etc/hostname
		fi
				
                #Network Parameters
                eval t=\($infoTab\)
		for elt in "${t[@]}"
        	do			
			interface=`echo $elt | cut -d '/' -f 1`
			mode=`echo $elt | cut -d '/' -f 2`
			ip=`echo $elt | cut -d '/' -f 3`
			netmask=`echo $elt | cut -d '/' -f 4`
			gateway=`echo $elt | cut -d '/' -f 5`
					
		        bootproto=`cat /tmp/$$-dir/etc/network/interfaces | grep "iface $interface" | cut -d ' ' -f '4'`
					
			if [ "$mode" = "dhcp" ]
			then		
				if [ "$bootproto" = "static" ]
				then
					ligne=`cat /tmp/$$-dir/etc/network/interfaces | grep -n "iface $interface inet" | cut -d ':' -f 1`
					sed -i 's/iface '$interface' inet static/iface '$interface' inet dhcp/' /tmp/$$-dir/etc/network/interfaces
					ligne=`expr $ligne + 1`
					sed -i ''$ligne'd' /tmp/$$-dir/etc/network/interfaces
					sed -i ''$ligne'd' /tmp/$$-dir/etc/network/interfaces
					sed -i ''$ligne'd' /tmp/$$-dir/etc/network/interfaces
				fi
	           	elif [ "$mode" = "static" ]
			then
				if [ "$bootproto" = "dhcp" ]
                 	        then
			 		sed -i 's/iface '$interface' inet dhcp/iface '$interface' inet static/g' /tmp/$$-dir/etc/network/interfaces
					ligne=`cat /tmp/$$-dir/etc/network/interfaces | grep -n "iface $interface inet" /tmp/$$-dir/etc/network/interfaces | cut -d: -f1`
					sed -i ''$ligne'a\gateway '$gateway'' /tmp/$$-dir/etc/network/interfaces
					sed -i ''$ligne'a\netmask '$netmask'' /tmp/$$-dir/etc/network/interfaces	
					sed -i ''$ligne'a\address '$ip'' /tmp/$$-dir/etc/network/interfaces
						
				elif [ "$bootproto" = "static" ]
				then
					ligne=`cat /tmp/$$-dir/etc/network/interfaces | grep -n "iface $interface" | cut -d ":" -f 1`
					ligne=`expr $ligne + 1`
					OldIP=`cat /tmp/$$-dir/etc/network/interfaces | awk 'NR == '$ligne'' | cut -d " " -f 2`
					ligne=`expr $ligne + 1`
					OldNetMask=`cat /tmp/$$-dir/etc/network/interfaces | awk 'NR == '$ligne'' | cut -d " " -f 2` 
					ligne=`expr $ligne + 1`
					OldGateway=`cat /tmp/$$-dir/etc/network/interfaces | awk 'NR == '$ligne'' | cut -d " " -f 2`
							
					sed -i 's/'$OldIP'/'$ip'/' /tmp/$$-dir/etc/network/interfaces
					sed -i 's/'$OldNetMask'/'$netmask'/' /tmp/$$-dir/etc/network/interfaces
					sed -i 's/'$OldGateway'/'$gateway'/' /tmp/$$-dir/etc/network/interfaces
				fi
			fi
		done
        else
                echo "Distrib Style not recognized, i prefer to don't touch anything"
	fi
fi
}

sourceVM=
infoTab=
indiceTab=0
name=
uuid=
newMac=
newIP=
newMask=
newGateway=
macAdress=
flagForce=0
flagNetwork=0
autostart=0
searchingForETCflag=

while [[ $1 = -* ]]; do
  case "$1" in
     --src)
      sourceVM="$2"
      shift 2
      ;;
     --name)
      name="$2"
      shift 2
      ;;
     --uuid)
      uuid="$2"
      shift 2
      ;;
     --mac)
      newMac="$2"
      shift 2
      ;;
     --hostname)
      newHostname="$2"
      flagNetwork=1
      shift 2
      ;;
     -f|--force)
      flagForce=1
      shift
      ;;
     -h|--help)
      usage
      exit
      ;;
      -a|--autostart)
      autostart=1
      shift
      ;;
      --net)
      tempVar="$2"
      stockVar
      flagNetwork=1
      shift 2
      ;;
      *)
      echo "Error: Unknown option: $1" >&2
      exit 1
      ;;
  esac
done

if [[ -z $name ]] || [[ -z $sourceVM ]]
then
     usage
     exit 1
fi

if [ "$flagForce" = "0" ]
then
	virsh list | grep -w $sourceVM > /dev/null 2>&1
        if [ "$?" = "0" ]
        then
                echo "Error : The Source VM is active, it's dangerous to clone an active VM"
		echo "You should Shutdown it"
		echo "Anyway, you can specify -f / --force if you want to bypass this security"
		exit 1
        fi
fi

##Cleaning old xml file in temp (if an error occured in the past)
rm -f /tmp/$$-vol.xml /tmp/$$-vm.xml
rmdir /tmp/$$-dir> /dev/null 2>&1

##First step : Cloning of the VM config file (XML format)
virsh dumpxml $sourceVM >> /tmp/$$-vol.xml
if [ "$?" -ne "0" ]
then
     echo "Error during the dump of the XML configuration file of the VM, does the Libvirt Daemon is running ? Does the VM really exist ???"
     exit 1
fi

##Second step : Editing the cloned XML, with new parameters


##/##/##/##/##/##/##/ TEST PURPOSE /##/##/##/##/##/##/##/##/

##Detect how many network interface the VM has##
macAddressTab=`cat /tmp/$$-vol.xml | grep "mac address" | cut -d "'" -f 2`

##/##/##/##/##/##/##/##/##/##/##/##/##/##/##/

#Change the uuid, the name, base image and Mac Address
OldCheminVM=`manipXML -v /tmp/$$-vol.xml -t "/domain/devices/disk[1]/source" -i file | cut -d '#' -f 2`
OldVMSourceFile=`basename $OldCheminVM`

#UUID
if [[ -z $uuid ]]
then
     newuuid=`uuidgen`
fi
manipXML -v /tmp/$$-vol.xml -t "/domain/uuid" -a $newuuid >> /dev/null

#Mac Address
if [[ -z $newMac ]]
then
 	macAddressTab[0]=00:16:3e:$(printf '%02x:%02x:%02x' $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)))
else
	macAddressTab[0]=$newMac
fi

eval t=\($macAddressTab\)
tempBoucle=1
for elt in "${t[@]}"
do
	manipXML -v /tmp/$$-vol.xml -t "/domain/devices/interface[$tempBoucle]/mac" -i address -a $elt >> /dev/null
	tempBoucle=`expr $tempBoucle + 1`
done



#Name
manipXML -v /tmp/$$-vol.xml -t "/domain/name" -a $name >> /dev/null

#Volume clone ! (.img only)
loopPart=`cat /tmp/$$-vol.xml | grep "source file" | cut -d "\"" -f 2`

eval z=\($loopPart\)
# XXX
imgIndex=0
for elt in "${z[@]}"
do
	isImg=`echo $elt | cut -d "." -f 2`
	if [[ "$isImg" = "img" ]]
	then
		if [[ $elt = $OldCheminVM ]] 
		then
			echo "Cloning volume $elt..."
			virsh vol-clone $OldCheminVM $name-$imgIndex.img > /dev/null 2>&1
			OldCheminVM=`dirname $OldCheminVM`
			NewCheminVM=$OldCheminVM/$name-$imgIndex.img
			sed -i "s#$elt#$NewCheminVM#g" /tmp/$$-vol.xml
			if [ "$?" -ne "0" ]
			then
				echo "Error during the clone of the source Image file"
				echo "Verify that the volume appears in the Xen Database, use virsh vol-list #pool#"
				echo "If the volume doesn't appears : virsh pool-refresh #pool#"
				exit 1
			fi
		else
			#eltclone=`basename $elt`
			#eltclone=`echo $eltclone | cut -d "." -f 1`
			#eltclone=$eltclone-clone.img
			eltclone=$name-$imgIndex.img
			echo "Cloning volume $elt..."
			virsh vol-clone $elt $eltclone > /dev/null 2>&1
			
			chemin=`dirname $elt`
			chemin="$chemin/"
			sed -i "s#$elt#$chemin$eltclone#g" /tmp/$$-vol.xml
		fi
	fi
	let imgIndex=imgIndex+1
done

##Fourth step : "install" the new VM in Xen
echo "Image files cloned, now defining the VM in Xen"
virsh define /tmp/$$-vol.xml > /dev/null 2>&1

## Editing cloned VM parameters (Hostname, Ip, etc...) ##
# Testing path to determine if VM is "Debian Style" or "Red Hat Style" #
if [ $flagNetwork = "1" ]
then
	loopPart=`cat /tmp/$$-vol.xml | grep "source file" | cut -d "\"" -f 2`
	mkdir /tmp/$$-dir> /dev/null 2>&1
	searchingForETCflag=0
	eval z=\($loopPart\)

	for elt in "${z[@]}"
	do
		loopPart2=`kpartx -l $elt | awk {'print $1'}`
		if [[ -z $loopPart2 ]]
		then
			mount -o loop $elt /tmp/$$-dir
			doManip
			umount /tmp/$$-dir
		else
			kpartx -a $elt
			eval t=\($loopPart2\)
			for elt in "${t[@]}"
			do
				mount /dev/mapper/$elt /tmp/$$-dir> /dev/null 2>&1
				doManip
				umount /tmp/$$-dir> /dev/null 2>&1
			done
			kpartx -d $elt > /dev/null 2>&1
		fi
	done
fi

##End : Cleaning
rm -f /tmp/$$-vol.xml /tmp/$$-vm.xml
rmdir /tmp/$$-dir> /dev/null 2>&1
echo "Job's done !"

if [[ $autostart = 1 ]]
then
	virsh start $name
fi
