VMware templates and OVF

VMware VCenter and OVF Environment properties together give us a great tool to achieve ultra-fast deployment and re-deployment of virtual machines. Since you can easily transport custom properties from the VCenter Settings page all the way into the guest OS, it's very simple to achieve truly low-touch deployment of ready-made templates.

I now have a template for a Ubuntu 9.04 Server system set up and customized to my basic needs, that can be deployed, including hostname, mailname and network configuration, within just a few minutes. Using VLANs, I can assign this machine to any network - and even move it to another one without even logging in to the Linux system (although in this case I fall back on a Windows-style reboot, just to make it easy... :) ).

The basic building blocks are these, given a fully configured virtual machine to be used as a template:

Properties in VCenter

On the Options tab of the Settings dialog in VCenter, you can define a number of properties (vApp Options | Advanced | Properties) which can be made visible inside the guest system, after you set up some basic configuration in other sections under vApp Options. I chose to use "VMware Tools" as my "OVF Environment Transport".

The properties I've defined are

ipIP address
netmaskNetwork mask
gatewayDefault gateway
hostnameHost name
domainDomain name (for the "hosts" file and for the mailname
dns1First DNS server
dns2Second DNS server
srchdomainsDNS search domains, space separated

Reading the properties in the guest OS

If you, like me, used "VMware Tools" as the transport method for the environment data, you can read the OVF environment inside the guest OS using the command

vmware-guestd --cmd 'info-get guestinfo.ovfEnv'

If you use the "ISO Image" method, you should get the same information through a virtual CD-ROM device that can be mounted in the guest OS.

What you get in both cases is an XML representation of the environment. You can find the schema through a link on this page if you need it.

To parse this properly, you need an XML parser. I decided to use an XSL transform, using Sablotron, which is available as a standard Ubuntu package. My XSL style sheet (/usr/local/etc/ovf.xsl) just selects the Properties from the XML file and produces a list of shell script variable assignments:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oe="http://schemas.dmtf.org/ovf/environment/1">
<xsl:output method="text" indent="no"/>
<xsl:template match="/">
<xsl:for-each select="oe:Environment/oe:PropertySection/oe:Property">
<xsl:text>export ovf_</xsl:text>
<xsl:value-of select="@oe:key"/>
<xsl:text>=&quot;</xsl:text>
<xsl:value-of select="@oe:value"/>
<xsl:text>&quot;&#xa;</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Running the transform is as simple as this:

vmware-guestd --cmd 'info-get guestinfo.ovfEnv' | sabcmd /usr/local/etc/ovf.xsl

The result looks something like this:

export ovf_dns1="208.67.220.220"
export ovf_dns2="208.67.222.222"
export ovf_domain="foo.nu"
export ovf_gateway="1.2.3.1"
export ovf_hostname="mybox"
export ovf_ip="1.2.3.4"
export ovf_netmask="255.255.255.192"
export ovf_srchdomains="apple.com microsoft.com"

The configuration script

This stuff is run at boot time only, and it will set the hostname (and domain name in /etc/hosts), the mailname, the ip address, netmask and gateway, the dns servers, the search domains, and the postfix mailhost data.

I use the ipcalc package to calculate network and broadcast address from the given data. ipcalc is also available as a standard Ubuntu package.

The script is built with nasty old shell script syntax, since that's what I default to when nothing is forcing me into another style. Whenever there are brackets with just a few spaces inside, they are most likely one space and one TAB character.
#!/bin/sh
 
export PATH=$PATH:/usr/sbin:/usr/bin:/usr/local/bin
IFFILE=/etc/network/interfaces
HOSTSFILE=/etc/hosts
RESOLVCONF=/etc/resolv.conf
HOSTNAMEFILE=/etc/hostname
MAILNAMEFILE=/etc/hostname
PFMAINCF=/etc/postfix/main.cf
XSLFILE=/usr/local/etc/ovf.xsl
TMPFILE=`mktemp`
 
ifcount=`grep "^iface " $IFFILE | grep -vc " lo "`
if [ "$ifcount" != 1 ]; then
    echo "Can't handle the format of $IFFILE. Too many interfaces?"
    exit 1
fi
 
eval `vmware-guestd --cmd 'info-get guestinfo.ovfEnv' | sabcmd $XSLFILE`
 
if [ ! -z "$ovf_dns1" -o ! -z "$ovf_dns2" -o ! -z "$ovf_dns" ]; then
    txt_dns="`echo '	dns-nameservers '$ovf_dns $ovf_dns1 $ovf_dns2 | sed 's/  */ /g'`"
    d_dns="`echo $ovf_dns $ovf_dns1 $ovf_dns2 | sed 's/  */ /g'`"
else
    txt_dns="`grep '^[ 	]dns-nameservers ' $IFFILE | head -1`"
    d_dns=`echo $txt_dns | sed 's/^.*dns-nameservers[ 	]*\(.*\)$[ 	]*/\1/'`
fi
 
if [ ! -z "$ovf_ip" ]; then
    txt_ip="	address $ovf_ip"
    d_ip=$ovf_ip
else
    txt_ip="`grep '^[ 	]address ' $IFFILE | head -1`"
    d_ip=`echo $txt_ip | sed 's/^.*address[ 	]*\(.*\)[ 	]*$/\1/'`
fi
 
if [ ! -z "$ovf_netmask" ]; then
    txt_netmask="	netmask $ovf_netmask"
    d_netmask=$ovf_netmask
else
    txt_netmask="`grep '^[ 	]netmask ' $IFFILE | head -1`"
    d_netmask=`echo $txt_netmask | sed 's/^.*netmask[ 	]*\(.*\)$/\1/'`
fi
 
d_network=`ipcalc -nb $d_ip/$d_netmask | grep "^Network:" | sed 's/^.*:[     ]*\([0-9.]*\)\/.*$/\1/'`
txt_network="	network $d_network"
 
d_broadcast=`ipcalc -nb $d_ip/$d_netmask | grep "^Broadcast:" | sed 's/^.*:[     ]*\([0-9.]*\).*$/\1/'`
txt_broadcast="	broadcast $d_broadcast"
 
if [ ! -z "$ovf_gateway" ]; then
    txt_gateway="	gateway $ovf_gateway"
    d_gateway=$ovf_gateway
else
    txt_gateway="`grep '^[ 	]gateway ' $IFFILE | head -1`"
    d_gateway=`echo $txt_gateway | sed 's/^.*gateway[ 	]*\(.*\)$/\1/'`
fi
 
if [ ! -z "$ovf_srchdomains" ]; then
    txt_srchdomains="	dns-search $ovf_srchdomains"
    d_srchdomains=$ovf_srchdomains
else
    txt_srchdomainis="`grep '^[ 	]dns-search ' $IFFILE | head -1`"
    d_srchdomainis="`echo $txt_srchdomains | sed 's/^.*dns-search[ 	]*\(.*\)$/\1/'`"
fi
 
d_hostname=`hostname 2>/dev/null`
if [ ! -z "$ovf_hostname" -a "$ovf_hostname" != "$d_hostname" ]; then
    d_hostname=$ovf_hostname
    hostname "$d_hostname"
    echo "$d_hostname" > $HOSTNAMEFILE
fi
 
d_domain=`hostname -d 2>/dev/null`
if [ ! -z "$ovf_domain" -a "$ovf_domain" != "$current_domain" ]; then
    d_domain=$ovf_domain
fi
 
cat > $TMPFILE <<EOF
127.0.0.1	localhost
$d_ip	$d_hostname.$d_domain	$d_hostname
 
# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
EOF
 
if diff $HOSTSFILE $TMPFILE > /dev/null 2>&1; then
    :
else
    echo " * OVF: Changing hostname/domain name"
    mv $TMPFILE $HOSTSFILE
    if [ -x /etc/init.d/syslog-ng ]; then
        /etc/init.d/syslog-ng restart
    fi
fi
 
if [ -f $PFMAINCF ]; then
   sed -e 's/^myhostname = .*$/myhostname = '$d_hostname.$d_domain'/'  -e 's/^mydestination = [^,]*\(, local.*\)$/mydestination = '$d_hostname.$d_domain'\1/' $PFMAINCF > $TMPFILE
   if diff $PFMAINCF $TMPFILE > /dev/null; then
      :
   else
	echo " * OVF: Chainging mailname"
	mv $TMPFILE $PFMAINCF
	echo "$d_hostname.$d_domain" > $MAILNAMEFILE
   fi
fi
 
cat > $TMPFILE <<EOF
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
 
# The loopback network interface
auto lo
iface lo inet loopback
 
# The primary network interface
auto eth0
iface eth0 inet static
$txt_ip
$txt_netmask
$txt_network
$txt_broadcast
$txt_gateway
	# dns-* options are implemented by the resolvconf package, if installed
$txt_dns
$txt_srchdomains
EOF
 
if diff $IFFILE $TMPFILE > /dev/null 2>&1; then
    rm $TMPFILE
else
    echo " * OVF: Changing network config"
    mv $TMPFILE $IFFILE
    /etc/init.d/networking restart
fi
 
echo "search $d_srchdomains" > $TMPFILE
for ns in $d_dns; do
    echo nameserver $ns >> $TMPFILE
done
 
mv $TMPFILE $RESOLVCONF

I run this using the sysvinit subsystem, using an init script that looks like this:

#!/bin/sh
# Basic support for IRIX style chkconfig
# chkconfig: 235 19 08
# description: Manages the OVF data
 
# Basic support for the Linux Standard Base Specification 1.0.0 (to be used by
# insserv for example)
### BEGIN INIT INFO
# Provides: vmware-tools
# Required-Start: $network
# Required-Stop: $network
# Default-Start: 2 3 5
# Default-Stop: 0 6
# Description: Manages the OVF data
### END INIT INFO
 
main()
{
   # See how we were called.
   case "$1" in
      start)
	   /usr/local/bin/ovfconfig.sh
	   ;;
      *)
         echo "Usage: `basename "$0"` {start|stop|status|restart|force-reload}"
         exit 1
   esac
 
   exit 0
}
 
main "$@"

This script is configured to run after vmware-tools has started.

Conclusion

With all this in place, I can deploy, configure and start a new Linux system without any in-guest customization, in about five minutes. This can of course be improved in many ways but it does show the possibilities of this system.

All three files are in the attached archive, so use that instead of cut and paste.

AttachmentSize
ovfconfig.tar.gz2.23 KB

Comments

Thanks for a great post.

I was wondering how you found the ovfEnv variable in
vmware-guestd --cmd 'info-get guestinfo.ovfEnv'

And if you know a document that shows other variables. The only one that I have been able to find at the moment is IP

I am hoping to get the display name of the VM to set its hostname.

Hi! I don't remember where I originally found the info, but this seems to be a good guide: http://www.vmware.com/support/developer/scripting-API/doc/Scripting_API.pdf

/Hans