[Home] [Blog] [Contact] - [Talks] [Workshop] [Bio] [Customers]
twitter linkedin youtube github rss

Patrick Debois

Controlling virtual Machines with an API

In the old days, getting a new machine could take days. It required ordering of hardware and putting everything together. Now in the these virtual/cloud days, creating new machines is a breeze. While a lot of effort is spent on automating the installation of the machine OS and its application, I see that the provisioning of a virtual machine is often still done by the GUI. So why not automate that step too.

Depending on the virtualization platform you choose, different options exist ranging from GUI (HTTP Posts), Command Lines, SOAP, XML-RPC based or language bindings. What follows is a list of ways I found. Again my experience is that most programming oriented XML-RPC, SOPA or Language Bindings are a subset of the commandline interface.

In all cases, the commandline API is updated first and then the rest follows. Again, this strengthens me to say that when automating the creation within Ruby you should actually write a wrapper around the commandline API. Because the target audience is sysadmins, this makes a lot of sense. At the end I provide an example using VirtualBox CommandLine API wrapped in Ruby Language.

Vmware

Vmware is one of the most used virtualization tools. These are some of the ways it can be scripted:

LibVirt

LibVirt tries to be virtual machine neutral: it has an abstraction for: Xen hypervisor on Linux and Solaris hosts, QEMU emulator, KVM Linux hypervisor, LXC Linux container system, OpenVZ Linux container system, User Mode Linux paravirtualized kernel. It also has experimental support for Virtualbox and Vmware ESX and GSX hypervisors but I found these unstable.

At lot of enterprise management tools use libvirt to build on:

Solaris Zones

Sun take another approach with their zones. They provide an excellent command API to manage their zones. http://www.sun.com/bigadmin/content/zones/. Beautifully for scripting.

Controlling Physical Machines

Even non virtual machines can be controlled: you can use wakeonlan, ipmitool or some management interface to power up/down the machines. Cobbler has this kind of powermanagement intergface https://fedorahosted.org/cobbler/wiki/PowerManagement to manage bullpap, wti, apc_snmp, ether-wake, ipmilan, drac, ipmitool, ilo, rsa , lpar, bladecenter.

Virtualbox

Virtualbox has one of the most excellent command Line available. And they generate their SOAP API from the same source!

Example of the Soap Interface

require 'soap/wsdlDriver'
require 'pp'

WSDL_URL="vboxwebService.wsdl"

soap = SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver
soap.wiredump_dev=STDERR
#soap = SOAP::WSDLDriverFactory.new(WSDL_URL).create_rpc_driver("vboxService", "vboxServicePort")
#pp soap.methods
vbox=soap.IWebsessionManager_logon({:username => '', :password => ''})
puts "Sessions"+vbox.returnval
version=soap.IVirtualBox_getVersion({:_this => vbox.returnval})
puts version.returnval
disks=soap.IVirtualBox_getHardDisks({:_this => vbox.returnval})
diskids=disks.returnval
diskids.each do |diskid| 
  type=soap.IHardDisk_getType({:_this => diskid })
  size=soap.IHardDisk_getLogicalSize({:_this => diskid })
  location=soap.IMedium_getLocation({:_this => diskid })
  puts diskid+"-"+type.returnval+"-"+size.returnval+location.returnval
end

My commandline based abstraction in Ruby

require "rubygems"
require "open4"
require "pp"
require "systr/commands"

#if disk has not been specified with fullname then is stores it in the default VBOX Location

def wait_for_state_vmachine(vmname, state, options={ })
  defaults={ :timeout => 1000 , :pollrate => 5 }
  options=defaults.merge(options) 
  
  begin
    Timeout::timeout(options[:timeout]) do
    while true do
        begin
          puts "polling state"
          actualstate=state_vmachine(vmname)
          if actualstate==state
            return true
          else
            puts "Currentstate: "+actualstate
            sleep options[:pollrate]
          end 
        end
      end
    end
  rescue Timeout::Error
    raise 'timeout waiting for machine to reach state #{state}'
  end
  
end

def state_vmachine(vmname)
  result=Command.execute("VBoxManage showvminfo #{vmname} --machinereadable|grep VMState=|cut -d '=' -f 2|cut -d '"+'"'+"' -f 2")
  state=result.stdout.to_s
  return state
end


def remove_vmachine(vmname,options={})

  defaults={ :disk => vmname }

  options=defaults.merge(options)

    if (state_vmachine(vmname)!="poweroff")
      Command.comment("Can't remove a running machine")
      throw "machine is still running"
    end
    
    Command.execute("VBoxManage modifyvm #{vmname} -sataport1 none")
    #Command.execute("VBoxManage snapshot #{vmname} discardcurrent --all")

    Command.execute("VBoxManage closemedium disk #{vmname}.vdi")
  
    Command.execute("VBoxManage unregistervm #{vmname} --delete")
    
  
  #first stop machine
  
  #then unregister
  
  #then remove it?
end

def exists_vmachine(vmname)
    return Command.test("VBoxManage showvminfo #{vmname}")
end

def floppy_kickstart_vmachine (vmname)
  #http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
  Command.execute("VBoxManage controlvm #{vmname} keyboardputscancode 26 17 31 16 2d 39 25 1f 0d 21 26 18 19 19 15 1c")

end

def add_floppy_vmachine(vmname,floppyfile)
  Command.execute("VBoxManage modifyvm #{vmname} -floppy #{floppyfile}")
end

def create_vmachine(vmname,options={})

  defaults={:ostype => 'RedHat', :memory => '384', :disk => vmname, :net => 'pxenet'}
   

  options=defaults.merge(options)

  Command.execute("VBoxManage createvm -name #{vmname} -ostype #{options[:ostype]} -register")
# Trying hostonly 
#  Command.execute("VBoxManage modifyvm #{vmname} -nic1 nat -nic2 intnet -intnet2 #{options[:net]}")
# http://www.virtualbox.org/manual/UserManual.html#networkingdetails

  
  Command.execute("VBoxManage modifyvm #{vmname} #{options[:network]}")

  #TODO: VBoxManage modifyvm puppet1 -macaddress1 aaaabbbbcc01


  Command.execute("VBoxManage modifyvm #{vmname} -memory #{options[:memory]}")
  Command.execute("VBoxManage modifyvm #{vmname} -sata on -sataport1 #{options[:disk]}.vdi -sataportcount 1")

  unless options[:dvd].nil?
    Command.execute("VBoxManage modifyvm #{vmname} -dvd #{options[:dvd]}")
  else
    Command.execute("VBoxManage modifyvm #{vmname} -dvd none")
  end
  
  unless options[:floppy].nil?
    Command.execute("VBoxManage modifyvm #{vmname} -floppy #{options[:floppy]}")
  else
    Command.execute("VBoxManage modifyvm #{vmname} -floppy empty")
    
  end

  Command.execute("VBoxManage modifyvm #{vmname} --vram 32")
  Command.execute("VBoxManage modifyvm #{vmname} --bioslogodisplaytime 0")
#  Command.execute("VBoxManage modifyvm #{vmname} --bioslogoimagepath path-to-256-bmp")

  Command.execute("VBoxManage modifyvm #{vmname} --acpi on")
  Command.execute("VBoxManage modifyvm #{vmname} --ioapic on")

  Command.execute("VBoxManage modifyvm #{vmname} -boot1 disk")
  Command.execute("VBoxManage modifyvm #{vmname} -boot2 dvd")
  Command.execute("VBoxManage modifyvm #{vmname} -boot3 net")

  #suppress interactive messages
  Command.execute("VBoxManage setextradata global 'GUI/RegistrationData' 'triesLeft=0'")
  Command.execute("VBoxManage setextradata global 'GUI/UpdateDate' '1 d, 2009-09-20'")
  Command.execute("VBoxManage setextradata global 'GUI/SuppressMessages' ',confirmInputCapture,remindAboutAutoCapture'")

end

def remove_dhcp
  result=Command.execute("VBoxManage list dhcpservers| grep NetworkName:|cut -d '-' -f 2").stdout
  result.each do |interface|
    Command.execute("VBoxManage  dhcpserver remove --ifname #{interface}")
  end

#  ERROR: Assertion failed at '/Users/vbox/tinderbox/3.0-mac-rel/src/VBox/Main/VirtualBoxImpl.cpp' (1706) in virtual nsresult VirtualBox::SetExtraData(const PRUnichar*, const PRUnichar*).
#  Unexpected exception 'N3xml12EIPRTFailureE' (Runtime error: -250 (Unresolved (unknown) device i/o error.)).
#  Please contact the product vendor!
#  Details: code NS_ERROR_FAILURE (0x80004005), component VirtualBox, interface IVirtualBox, callee nsISupports
#  Context: "EnableStaticIpConfig(Bstr(pIp), Bstr(pNetmask))" at line 267 of file VBoxManageHostonly.cpp

  Command.execute("VBoxManage hostonlyif ipconfig vboxnet0 --ip 192.168.10.1 --netmask 255.255.255.0")
  
end

def start_vmachine(vmname)
  Command.comment("starting virtual machine #{vmname}")
  if ENV['SYSTR_HEADLESS'].nil?
    Command.execute("VBoxManage startvm  #{vmname}")  
  else
    system("VBoxHeadless -s #{vmname} --vrdp off &") 
    sleep 4 
  end
end

def stop_vmachine(vmname)
  Command.comment("stopping virtual machine #{vmname}")
  Command.execute("VBoxManage controlvm  #{vmname} poweroff")    
end

def remove_snapshot_vmachine(vmname,snapname)
  Command.execute("VBoxManage snapshot #{vmname} discard #{snapname}")  

end

def create_snapshot_vmachine(vmname,snapname)
  Command.execute("VBoxManage snapshot #{vmname} take #{snapname}")  
end

def ssh_enable_vmachine(vmname, options={})
  defaults={:localport => 2222 , :remoteport => 22}
  options=defaults.merge(options)
  
  Command.execute("VBoxManage setextradata #{vmname} 'VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/HostPort' #{options[:localport]}")
  Command.execute("VBoxManage setextradata #{vmname} 'VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/GuestPort' #{options[:remoteport]}")
  Command.execute("VBoxManage setextradata #{vmname} 'VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/Protocol' TCP")
  return options[:port]
end