Just Enough Documented Information

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

  1 
  2 require "rubygems"
  3 require "open4"
  4 require "pp"
  5 require "systr/commands"
  6 
  7 #if disk has not been specified with fullname then is stores it in the default VBOX Location
  8 
  9 def wait_for_state_vmachine(vmname, state, options={ })
 10   defaults={ :timeout => 1000 , :pollrate => 5 }
 11   options=defaults.merge(options) 
 12   
 13   begin
 14     Timeout::timeout(options[:timeout]) do
 15     while true do
 16         begin
 17           puts "polling state"
 18           actualstate=state_vmachine(vmname)
 19           if actualstate==state
 20             return true
 21           else
 22             puts "Currentstate: "+actualstate
 23             sleep options[:pollrate]
 24           end 
 25         end
 26       end
 27     end
 28   rescue Timeout::Error
 29     raise 'timeout waiting for machine to reach state #{state}'
 30   end
 31   
 32 end
 33 
 34 def state_vmachine(vmname)
 35   result=Command.execute("VBoxManage showvminfo #{vmname} --machinereadable|grep VMState=|cut -d '=' -f 2|cut -d '"+'"'+"' -f 2")
 36   state=result.stdout.to_s
 37   return state
 38 end
 39 
 40 
 41 def remove_vmachine(vmname,options={})
 42 
 43   defaults={ :disk => vmname }
 44 
 45   options=defaults.merge(options)
 46 
 47     if (state_vmachine(vmname)!="poweroff")
 48       Command.comment("Can't remove a running machine")
 49       throw "machine is still running"
 50     end
 51     
 52     Command.execute("VBoxManage modifyvm #{vmname} -sataport1 none")
 53     #Command.execute("VBoxManage snapshot #{vmname} discardcurrent --all")
 54 
 55     Command.execute("VBoxManage closemedium disk #{vmname}.vdi")
 56   
 57     Command.execute("VBoxManage unregistervm #{vmname} --delete")
 58     
 59   
 60   #first stop machine
 61   
 62   #then unregister
 63   
 64   #then remove it?
 65 end
 66 
 67 def exists_vmachine(vmname)
 68     return Command.test("VBoxManage showvminfo #{vmname}")
 69 end
 70 
 71 def floppy_kickstart_vmachine (vmname)
 72   #http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
 73   Command.execute("VBoxManage controlvm #{vmname} keyboardputscancode 26 17 31 16 2d 39 25 1f 0d 21 26 18 19 19 15 1c")
 74 
 75 end
 76 
 77 def add_floppy_vmachine(vmname,floppyfile)
 78   Command.execute("VBoxManage modifyvm #{vmname} -floppy #{floppyfile}")
 79 end
 80 
 81 def create_vmachine(vmname,options={})
 82 
 83   defaults={:ostype => 'RedHat', :memory => '384', :disk => vmname, :net => 'pxenet'}
 84    
 85 
 86   options=defaults.merge(options)
 87 
 88   Command.execute("VBoxManage createvm -name #{vmname} -ostype #{options[:ostype]} -register")
 89 # Trying hostonly 
 90 #  Command.execute("VBoxManage modifyvm #{vmname} -nic1 nat -nic2 intnet -intnet2 #{options[:net]}")
 91 # http://www.virtualbox.org/manual/UserManual.html#networkingdetails
 92 
 93   
 94   Command.execute("VBoxManage modifyvm #{vmname} #{options[:network]}")
 95 
 96   #TODO: VBoxManage modifyvm puppet1 -macaddress1 aaaabbbbcc01
 97 
 98 
 99   Command.execute("VBoxManage modifyvm #{vmname} -memory #{options[:memory]}")
100   Command.execute("VBoxManage modifyvm #{vmname} -sata on -sataport1 #{options[:disk]}.vdi -sataportcount 1")
101 
102   unless options[:dvd].nil?
103     Command.execute("VBoxManage modifyvm #{vmname} -dvd #{options[:dvd]}")
104   else
105     Command.execute("VBoxManage modifyvm #{vmname} -dvd none")
106   end
107   
108   unless options[:floppy].nil?
109     Command.execute("VBoxManage modifyvm #{vmname} -floppy #{options[:floppy]}")
110   else
111     Command.execute("VBoxManage modifyvm #{vmname} -floppy empty")
112     
113   end
114 
115   Command.execute("VBoxManage modifyvm #{vmname} --vram 32")
116   Command.execute("VBoxManage modifyvm #{vmname} --bioslogodisplaytime 0")
117 #  Command.execute("VBoxManage modifyvm #{vmname} --bioslogoimagepath path-to-256-bmp")
118 
119   Command.execute("VBoxManage modifyvm #{vmname} --acpi on")
120   Command.execute("VBoxManage modifyvm #{vmname} --ioapic on")
121 
122   Command.execute("VBoxManage modifyvm #{vmname} -boot1 disk")
123   Command.execute("VBoxManage modifyvm #{vmname} -boot2 dvd")
124   Command.execute("VBoxManage modifyvm #{vmname} -boot3 net")
125 
126   #suppress interactive messages
127   Command.execute("VBoxManage setextradata global 'GUI/RegistrationData' 'triesLeft=0'")
128   Command.execute("VBoxManage setextradata global 'GUI/UpdateDate' '1 d, 2009-09-20'")
129   Command.execute("VBoxManage setextradata global 'GUI/SuppressMessages' ',confirmInputCapture,remindAboutAutoCapture'")
130 
131 end
132 
133 def remove_dhcp
134   result=Command.execute("VBoxManage list dhcpservers| grep NetworkName:|cut -d '-' -f 2").stdout
135   result.each do |interface|
136     Command.execute("VBoxManage  dhcpserver remove --ifname #{interface}")
137   end
138 
139 #  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*).
140 #  Unexpected exception 'N3xml12EIPRTFailureE' (Runtime error: -250 (Unresolved (unknown) device i/o error.)).
141 #  Please contact the product vendor!
142 #  Details: code NS_ERROR_FAILURE (0x80004005), component VirtualBox, interface IVirtualBox, callee nsISupports
143 #  Context: "EnableStaticIpConfig(Bstr(pIp), Bstr(pNetmask))" at line 267 of file VBoxManageHostonly.cpp
144 
145   Command.execute("VBoxManage hostonlyif ipconfig vboxnet0 --ip 192.168.10.1 --netmask 255.255.255.0")
146   
147 end
148 
149 def start_vmachine(vmname)
150   Command.comment("starting virtual machine #{vmname}")
151   if ENV['SYSTR_HEADLESS'].nil?
152     Command.execute("VBoxManage startvm  #{vmname}")  
153   else
154     system("VBoxHeadless -s #{vmname} --vrdp off &") 
155     sleep 4 
156   end
157 end
158 
159 def stop_vmachine(vmname)
160   Command.comment("stopping virtual machine #{vmname}")
161   Command.execute("VBoxManage controlvm  #{vmname} poweroff")    
162 end
163 
164 def remove_snapshot_vmachine(vmname,snapname)
165   Command.execute("VBoxManage snapshot #{vmname} discard #{snapname}")  
166 
167 end
168 
169 def create_snapshot_vmachine(vmname,snapname)
170   Command.execute("VBoxManage snapshot #{vmname} take #{snapname}")  
171 end
172 
173 def ssh_enable_vmachine(vmname, options={})
174   defaults={:localport => 2222 , :remoteport => 22}
175   options=defaults.merge(options)
176   
177   Command.execute("VBoxManage setextradata #{vmname} 'VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/HostPort' #{options[:localport]}")
178   Command.execute("VBoxManage setextradata #{vmname} 'VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/GuestPort' #{options[:remoteport]}")
179   Command.execute("VBoxManage setextradata #{vmname} 'VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/Protocol' TCP")
180   return options[:port]
181 end
182 
183 
184