Tuesday, 22 July 2014

Starting VirtualBox 'host-only' interfaces before starting VMs

I really like VirtualBox. For any kind of testing, proofs-of-concept builds, or just trying out new stuff (like the latest Android x86 release), pretty much anything I need to do I can do in VirtualBox.

Occasionally, however, I come across something that doesn't quite work the way I need it to. I ran into one of those things while doing the development of my 'Oracle SE Physical Standby'.

The problem

Creating a RAC cluster in VirtualBox is reasonably straightforward. When you create multi-node Oracle RAC clusters, beginning with Oracle 11g Release 2, the clusterware provides a Single Client Access Name (SCAN) to simplify configuration of clients which need to access a RAC database. The SCAN is a single virtual host name that can resolve to multiple IPs. The Oracle clusterware creates and manages a Virtual IP (VIP) for each of those IPs, and a TNS listener listening on each of those VIPs. The VIPs and listeners can migrate to any node in the cluster. The minimum recommended IPs for the SCAN is 3. It is possible to have the SCAN defined in /etc/hosts, but then you are restricted to just one IP (and therefore one listener). The way to do it properly is to have the SCAN defined in a DNS server.

One of the network options in VirtualBox is a 'host-only' network. This creates a private network between all the guest virtual machines on that network, and the VirtualBox host machine.  However, by default none of the guest VMs have network access beyond the host machine, so to provide DNS, ideally you have a DNS server running on your host machine, and in order for the guest VMs to use the DNS server on the host machine, it must be listening on the host's 'host-only' network IP.  VirtualBox creates a network interface on the host machine for the 'host-only' network, and assigns an IP to that interface on the 'host-only' network. The final thing is that in order for the clusterware on each guest to start correctly, it needs to be able to access the DNS server.

Now to the core of the problem. In order for named to bind to the 'host-only' IP, the interface has to be created, have an IP assigned, and be up. However, the interface for the VirtualBox 'host-only' network does not get created until you run VirtualBox, either the VirtualBox Manager GUI application, or running 'VBoxManage' on the command-line. And the interface does not get configured with an IP and brought up until a guest VM that uses that 'host-only' network is started.  So what I have to do is start a VM, and while it is starting, quickly restart named so it will bind to the newly-created 'host-only' interface on the host system, before the clusterware tries to start. Not my ideal way of working.

The solution

So, having established that just running 'VBoxManage list hostonlyifs' will not only create the interfaces on the host, but also give all the information required to configure each interface, it was just left to write a script to do just that.  Now I can run the script, restart named and then start the guest VMs, all in my own time.

The script

#!/bin/bash
# vboxstartnet.sh

bcastcalc(){

    local IFS='.' ip i
    local -a oct msk
   
    read -ra oct <<<"$1"
    read -ra msk <<<"$2"

    for i in ${!oct[@]}; do
        ip+=( "$(( oct[i] + ( 255 - ( oct[i] | msk[i] ) ) ))" )
    done

    echo "${ip[*]}"
}

mask2cidr() {

    local nbits dec
    local -a octets=( [255]=8 [254]=7 [252]=6 [248]=5 [240]=4
                      [224]=3 [192]=2 [128]=1 [0]=0           )
   
    while read -rd '.' dec; do
        [[ -z ${octets[dec]} ]] && echo "Error: $dec is not recognised" && exit 1
        (( nbits += octets[dec] ))
        (( dec < 255 )) && break
    done <<<"$1."

    echo "/$nbits"

}

for line in `VBoxManage list hostonlyifs | sed 's/: */=/' | grep '^Name\|^IPAddr\|^NetworkMask'`
do
eval $line
case "$line" in
    NetworkMask* )
           BCAST=`bcastcalc "$IPAddress" "$NetworkMask"`
           CIDR=`mask2cidr "$NetworkMask"`
           sudo /bin/ip addr add ${IPAddress}${CIDR} broadcast ${BCAST} dev ${Name}
           sudo /bin/ip link set dev ${Name} up
       ;;
esac
done
 

No comments:

Post a Comment