Just Enough Developed Infrastructure

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

<% codify(:lang => "ruby" , :line_nubers => "inline") do %> 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

<% end %>

My commandline based abstraction in Ruby

<% codify(:lang => "ruby", :line_numbers => "inline") do %> 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

<% end %>