Pages

Tuesday 28 December 2010

VirtualBox: Managing Virtual Machines

Virtual Box is a virtualization software that allows many operating systems (guests) to run simultaneously on a host operating system. In the concept of cloud computing, it is increasingly common for server operating systems to be run in a virtual machine. The guest operating systems can be run in either headed or headless modes. The difference is that in "Headed" mode, the guest displays an interface similar to that displayed when the monitor is connected to the system.

It is ideal of run the guest operating systems that run as servers in headless mode.
The script below does just that. It is a bit buggy but works just fine. It was developed on Ubuntu but I expect it to run suitably on other Debian systems. It can be customized to run on other Linux/ UNIX distros.


   1 #!/bin/sh
   2 #Author: Emmanuel Toko
   3 #/etc/init.d/vbox_mgmt
   4 #
   5 ### BEGIN INIT INFO
   6 # Provides:          vm_mgmt
   7 # Required-Start:    $network $remote_fs $VBoxManage $VBoxHeadless $syslog
   8 # Required-Stop:     $network $remote_fs $VBoxManage $VBoxHeadless $syslog
   9 # Default-Start:     2 3 4 5
  10 # Default-Stop:      0 1 6
  11 # Short-Description: Start VMs in headless mode.
  12 # Description:       Run VMs for the default VirtualBox user in
  13 #                    headless mode. Make sure all VMs are using different RDP
  14 #                    ports.
  15 ### END INIT INFO
  16 
  17 VBOX_USER=chapati
  18 
  19 # The list of VMs to run. Leave empty to run all registered VMs.
  20 VBOX_LIST=""
  21 
  22 # VirtualBox executables
  23 VBOX_MANAGE=/usr/bin/VBoxManage
  24 VBOX_HEADLESS=/usr/bin/VBoxHeadless
  25 
  26 # Do NOT "set -e"
  27 
  28 # PATH should only include /usr/* if it runs after the mountnfs.sh script
  29 PATH=/sbin:/usr/sbin:/bin:/usr/bin
  30 DESC="VirtualBox daemon"
  31 NAME=vm_mgmt
  32 DAEMON=$VBOX_HEADLESS
  33 DAEMON_ARGS=""
  34 PIDFILE=/var/run/$NAME.pid
  35 #SCRIPTNAME=/etc/init.d/$NAME
  36 SCRIPTNAME=/etc/init.d/$NAME
  37 BATTERY_THRESHOLD=3
  38 # Exit if the package is not installed
  39 [ -x "$DAEMON" ] || exit 0
  40 
  41 # Read configuration variable file if it is present
  42 [ -r /etc/default/$NAME ] && . /etc/default/$NAME
  43 
  44 # Load the VERBOSE setting and other rcS variables
  45 . /lib/init/vars.sh
  46 
  47 # LSB log_* functions.
  48 . /lib/lsb/init-functions
  49 
  50 vm_init_list()
  51 {
  52     CHECK_VM_TYPE=0
  53     LIST_VMS=""
  54      # get registered VMs
  55      
  56      if [ "$CHECK_VM_TYPE" -eq "$1" ]
  57      then
  58          LIST_VMS=`sudo -H -u $VBOX_USER $VBOX_MANAGE --nologo list vms | cut -d ' ' -f 1 | tr -d '"'`
  59      else
  60          LIST_VMS=`sudo -H -u $VBOX_USER $VBOX_MANAGE --nologo list runningvms | cut -d ' ' -f 1 | tr -d '"'`
  61      fi
  62    
  63     # check for list of VMs
  64     if [ -z "$VBOX_LIST" ]
  65     then
  66             # all registered VMs for user
  67             VBOX_LIST=$LIST_VMS
  68     else
  69       # check that VMs exist
  70       for VM in $VBOX_LIST
  71       do
  72          case $LIST_VMS in
  73          "$VM")
  74             continue
  75             ;;
  76          *)
  77             log_failure_msg "ERROR: VM '$VM' is not registered!"
  78             exit 1
  79             ;;
  80          esac
  81       done
  82    fi
  83 }
  84 
  85 # get uuid for vm
  86 vm_get_uuid()
  87 {
  88    vm=$1
  89    hwuuid=`sudo -H -u $VBOX_USER $VBOX_MANAGE --nologo showvminfo --machinereadable "$vm" | grep 'hardwareuuid='`
  90    echo $hwuuid | cut -d '=' -f 2 | tr -d '"'
  91 }
  92 
  93 # control running vm
  94 vm_ctrl()
  95 {
  96    sudo -H -u $VBOX_USER $VBOX_MANAGE --nologo controlvm $1 $2 > /dev/null 2>&1
  97 }
  98 
  99 #
 100 # Function that starts the daemon/service
 101 #
 102 do_start()
 103 {
 104     #First do a check on the amount of battery that the system has at this point
 105     BATTERY_LEVEL=0
 106     
 107     #acpi outputs battery info like so: Battery 0: Discharging, 93%, 07:07:03 remaining
 108     BATTERY_LEVEL=`acpi | awk '{print $4}' | tr -d "%,"`
 109     
 110     if [ "$BATTERY_LEVEL" -le "$BATTERY_THRESHOLD" ]
 111     then
 112         log_failure_msg ":( The battery level is low [$BATTERY_LEVEL%] so halting...."
 113         exit 1
 114     fi
 115     
 116     vm_init_list 0
 117     
 118     # Return
 119     #   0 if daemon has been started
 120     #   1 if daemon was already running
 121     #   2 if daemon could not be started
 122     RETVAL=0
 123     
 124     VM_EXISTS=0
 125     FALSE=0
 126     TRUE=1
 127     
 128     #Internal function that will "safely" a VM or VMS. Function takes one.. 
 129     #argument i.e. the VM name and attempts to start that VM
 130     start_vm()
 131     {
 132         VM=$1
 133         VM_UUID=`vm_get_uuid $VM`
 134         VM_PIDFILE="$PIDFILE.$VM_UUID"
 135         VM_DAEMON="$DAEMON"
 136         VM_DAEMON_ARGS="$DAEMON_ARGS --startvm $VM_UUID"
 137           
 138         log_action_begin_msg "Starting VM '$1'"
 139           
 140         # test for running VM
 141         USER=$VBOX_USER LOGNAME=$VBOX_USER start-stop-daemon \
 142             --start \
 143             --quiet \
 144             --pidfile $VM_PIDFILE \
 145             --startas $VM_DAEMON \
 146             --test \
 147             > /dev/null
 148           
 149         # VM already running
 150         if [ "$?" != 0 ]
 151         then
 152             # report VM is running
 153             log_warning_msg "VM '$1' already running"
 154             [ "$RETVAL" = 0 ] && RETVAL=1
 155             continue
 156         fi
 157       
 158         # start VM
 159         USER=$VBOX_USER LOGNAME=$VBOX_USER start-stop-daemon \
 160          --start \
 161          --quiet \
 162          --pidfile $VM_PIDFILE \
 163          --make-pidfile \
 164          --background \
 165          --chuid $VBOX_USER \
 166          --startas $VM_DAEMON \
 167          -- $VM_DAEMON_ARGS
 168           
 169      log_action_end_msg "$?"
 170           
 171      # check if start failed
 172      if [ "$?" != 0 ]
 173      then
 174          # report error
 175          log_failure_msg "Error starting VM '$VM' :("
 176          RETVAL=2
 177      fi
 178       
 179      RETVAL="$?"
 180             
 181      log_action_end_msg "$RETVAL"
 182     exit 0
 183     }
 184     
 185     if [ -n "$1" ]
 186     then
 187         for VM in $VBOX_LIST
 188         do
 189             if [ "$VM" = "$1" ]
 190             then
 191                 VM_EXISTS=1
 192             fi
 193         done
 194         
 195         if [ "$VM_EXISTS" -eq "$TRUE" ]
 196         then
 197             start_vm $1 
 198         else
 199             log_failure_msg "VM $1 is not a registered VM"
 200             echo "Enter one VM name from the list below[VM names are case sensitive];"
 201             
 202             for VM_1 in $VBOX_LIST
 203             do
 204                 echo "$VM_1"
 205             done
 206             exit 1
 207         fi
 208         
 209         VM_EXISTS=0
 210         check_battery
 211         return
 212     fi
 213    
 214     # Start all VMs
 215     for VM in $VBOX_LIST
 216     do
 217         start_vm $VM
 218     done
 219    
 220    if [ "$RETVAL" -lt 2 ]
 221    then
 222       log_daemon_msg "VirtualBox daemon started successfully"
 223    else
 224       log_daemon_msg "VirtualBox daemon started with errors"
 225    fi
 226    
 227    check_battery
 228    
 229    return "$RETVAL"
 230 }
 231 
 232 #
 233 # Function that stops the daemon/service
 234 #
 235 do_stop()
 236 {
 237    vm_init_list 1
 238    
 239    # Return
 240    #   0 if daemon has been stopped
 241    #   1 if daemon was already stopped
 242    #   2 if daemon could not be stopped
 243    #   other if a failure occurred
 244    RETVAL=0
 245     VM_EXISTS=0
 246     FALSE=0
 247     TRUE=1
 248     
 249    #Function "safely" stops and running VM instance. Call this function in a 
 250    #loop to stop all running VM instances (Not the best way but still good)
 251    stop_vm()
 252    {
 253        VM=$1
 254        VM_UUID=`vm_get_uuid $VM`
 255        VM_PIDFILE="$PIDFILE.$VM_UUID"
 256       
 257        log_action_begin_msg "Stopping VM '$VM'"
 258       
 259        # try savestate halt
 260        vm_ctrl $VM savestate
 261       
 262        # stop daemon
 263       USER=$VBOX_USER LOGNAME=$VBOX_USER start-stop-daemon \
 264          --stop \
 265          --quiet \
 266          --retry=TERM/30/KILL/5 \
 267          --pidfile $VM_PIDFILE
 268       
 269       case "$?" in
 270       0)
 271          log_action_end_msg 0
 272          ;;
 273       1)
 274          log_warning_msg "VM '$VM' already stopped"
 275          [ "$RETVAL" = 0 ] && RETVAL=1
 276          ;;
 277       2)
 278          log_action_end_msg 1
 279          log_failure_msg "ERROR: Could not stop VM '$VM'"
 280          RETVAL=2
 281          continue
 282          ;;
 283       esac
 284       
 285       rm -f $VM_PIDFILE
 286    }
 287    
 288    if [ -n "$1" ]
 289     then
 290         for VM in $VBOX_LIST
 291         do
 292             if [ "$VM" = "$1" ]
 293             then
 294                 VM_EXISTS=1
 295             fi
 296         done
 297         
 298         if [ "$VM_EXISTS" -eq "$TRUE" ]
 299         then
 300             stop_vm $1  
 301         else
 302             log_failure_msg "VM $1 is not a registered VM"
 303             echo "Enter the VM name from the list of running VMS below;"
 304             
 305             for VM_1 in $VBOX_LIST
 306             do
 307                 echo "$VM_1"
 308             done
 309             
 310             exit 1
 311         fi
 312         
 313         VM_EXISTS=0
 314         return
 315     fi
 316    
 317    for VM in $VBOX_LIST
 318    do
 319       stop_vm $VM
 320    done
 321    
 322    if [ "$RETVAL" -lt 2 ]
 323    then
 324       log_daemon_msg "VirtualBox daemon stopped successfully"
 325    else
 326       log_daemon_msg "VirtualBox daemon stopped with errors"
 327    fi
 328    
 329    return "$RETVAL"
 330 }
 331 
 332 #
 333 # Function that sends a SIGHUP to the daemon/service
 334 #
 335 do_reload() {
 336    #
 337    # If the daemon can reload its configuration without
 338    # restarting (for example, when it is sent a SIGHUP),
 339    # then implement that here.
 340    #
 341    start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
 342    return 0
 343 }
 344 
 345 check_battery() 
 346 {
 347     BATTERY_OK=true
 348     BATTERY_LEVEL=0
 349 
 350     while [ $BATTERY_OK ]
 351     do
 352         BATTERY_LEVEL=`acpi | awk '{print $4}' | tr -d "%,"`
 353         sleep 5
 354 
 355         if [ $BATTERY_LEVEL -le $BATTERY_THRESHOLD ]
 356         then
 357             echo "Low Battery! Halting any running VMs" | wall
 358             do_stop
 359             BATTERY_OK=false
 360             log_warning_msg "Battery is critically low: $BATTERY_LEVEL%"
 361         fi
 362     done
 363 }
 364 
 365 case "$1" in
 366   start)
 367    log_daemon_msg "Starting $DESC" "$NAME"
 368    do_start $2
 369    case "$?" in
 370       0|1) log_end_msg 0 ;;
 371       2) log_end_msg 1 ;;
 372    esac
 373    ;;
 374   stop)
 375    log_daemon_msg "Stopping $DESC" "$NAME"
 376    do_stop $2
 377    case "$?" in
 378       0|1) log_end_msg 0 ;;
 379       2) log_end_msg 1 ;;
 380    esac
 381    ;;
 382   status)
 383        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
 384        ;;
 385   #reload|force-reload)
 386    #
 387    # If do_reload() is not implemented then leave this commented out
 388    # and leave 'force-reload' as an alias for 'restart'.
 389    #
 390    #log_daemon_msg "Reloading $DESC" "$NAME"
 391    #do_reload
 392    #log_end_msg $?
 393    #;;
 394   restart|force-reload)
 395    #
 396    # If the "reload" option is implemented then remove the
 397    # 'force-reload' alias
 398    #
 399    log_daemon_msg "Restarting $DESC" "$NAME"
 400    do_stop
 401    case "$?" in
 402      0|1)
 403       do_start $2
 404       case "$?" in
 405          0) log_end_msg 0 ;;
 406          1) log_end_msg 1 ;; # Old process is still running
 407          *) log_end_msg 1 ;; # Failed to start
 408       esac
 409       ;;
 410      *)
 411         # Failed to stop
 412       log_end_msg 1
 413       ;;
 414    esac
 415    ;;
 416   *)
 417    
 418    echo "Usage: $SCRIPTNAME {start [VM]|stop [VM]|status|restart [VM]|force-reload [VM]}" >&2
 419    echo "\t Please note that 'start' [VM] indicates that the argument VM is optional f.g. you can write start OMRS"
 420    exit 3
 421    ;;
 422 esac
 423 
 424 :
 425