availability: January 2010
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 is one of the most used virtualization tools. These are some of the ways it can be scripted:
Command line - Using vmrun to Control Virtual Machines http://www.vmware.com/pdf/vix180_vmrun_command.pdf
GUI Vmware Studio - http://blog.organicelement.com/2008/09/17/vmware-studio-automated-production-of-virtual-machines/
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:
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.
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 has one of the most excellent command Line available. And they generate their SOAP API from the same source!
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
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