2 # Copyright (C) 2009 Red Hat, Inc.
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 # 1. You need VirtualBox and VBoxManage tool installed
20 # 2. You need to setup some images manually inside VirtualBox
21 # 3. You need to install 'Guest Additions' to this images for metrics
22 # 4. You need a lot of hard drive space ;-)
24 require 'deltacloud/base_driver'
29 class VboxDriver < Deltacloud::BaseDriver
31 VBOX_MANAGE_PATH = '/usr/bin'
38 :architecture => `uname -m`
44 :architecture => `uname -m`
50 :architecture => `uname -m`
56 :architecture => `uname -m`
69 define_hardware_profile 'small' do
73 architecture `uname -m`.strip
76 define_hardware_profile 'medium' do
80 architecture `uname -m`.strip
83 define_hardware_profile 'large' do
87 architecture `uname -m`.strip
90 define_instance_states do
91 create.to( :pending) .automatically
92 pending.to(:start) .automatically
93 start.to( :running ) .on( :create )
94 running.to( :running ) .on( :reboot )
95 running.to( :paused ) .on( :stop )
96 running.to(:finish) .on( :destroy )
97 paused.to( :running ) .on( :start )
98 poweroff.to( :start ) .on( :start)
101 def flavors(credentials, opts = nil)
105 def realms(credentials, opts = nil)
109 def images(credentials, opts = nil)
110 images = convert_images(vbox_client('list vms'))
111 images = filter_on( images, :id, opts )
112 images = filter_on( images, :architecture, opts )
113 images.sort_by{|e| [e.owner_id, e.description]}
116 def instances(credentials, opts = nil)
117 instances = convert_instances(vbox_client('list vms'))
118 instances = filter_on( instances, :id, opts )
119 instances = filter_on( instances, :state, opts )
120 instances = filter_on( instances, :image_id, opts )
124 def create_instance(credentials, image_id, opts)
125 instance = instances(credentials, { :image_id => image_id }).first
126 name = opts[:name] || "#{instance.name} - #{Time.now.to_i}"
128 # Create new virtual machine, UUID for this machine is returned
129 vm=vbox_client("createvm --name '#{name}' --register")
130 new_uid = vm.split("\n").select { |line| line=~/^UUID/ }.first.split(':').last.strip
132 # Add Hardware profile to this machine
138 flavor = FLAVORS.select { |f| f.id == opts[:flavor_id]}.first
139 memory = flavor.memory
140 ostype = "Windows" if flavor.id == 'microsoft'
141 cpu = "#{flavor.cpu}"
144 memory = ((memory*1.024)*1000).to_i
146 vbox_client("modifyvm '#{new_uid}' --ostype #{ostype} --memory #{memory} --vram 16 --nic1 bridged --bridgeadapter eth0 --cableconnected1 on --cpus #{cpu}")
149 # This will 'reuse' existing image
150 location = instance_volume_location(instance.id)
151 new_location = File.join(File.dirname(location), name+'.vdi')
153 # This need to be in fork, because it takes some time with large images
155 vbox_client("clonehd '#{location}' '#{new_location}' --format VDI")
156 vbox_client("storagectl '#{new_uid}' --add ide --name '#{name}'-hd0 --controller PIIX4")
157 vbox_client("storageattach '#{new_uid}' --storagectl '#{name}'-hd0 --port 0 --device 0 --type hdd --medium '#{new_location}'")
161 def reboot_instance(credentials, id)
162 vbox_client("controlvm '#{id}' reset")
165 def stop_instance(credentials, id)
166 vbox_client("controlvm '#{id}' pause")
169 def start_instance(credentials, id)
170 instance = instances(credentials, { :id => id }).first
171 # Handle 'pause' and 'poweroff' state differently
172 if 'POWEROFF'.eql?(instance.state)
173 vbox_client("startvm '#{id}'")
175 vbox_client("controlvm '#{id}' resume")
179 def destroy_instance(credentials, id)
180 vbox_client("controlvm '#{id}' poweroff")
183 def storage_volumes(credentials, opts = nil)
186 instances(credentials, {}).each do |image|
187 raw_image = convert_image(vbox_vm_info(image.id))
188 hdd_id = raw_image['ide controller-imageuuid-0-0'.to_sym]
190 volumes << convert_volume(vbox_client("showhdinfo '#{hdd_id}'"))
192 filter_on( volumes, :id, opts )
198 `#{VBOX_MANAGE_PATH}/VBoxManage -q #{cmd}`.strip
201 def vbox_vm_info(uid)
202 vbox_client("showvminfo --machinereadable '#{uid}'")
205 def convert_instances(instances)
207 instances.split("\n").each do |image|
208 image_id = image.match(/^\"(.+)\" \{(.+)\}$/).to_a.last
209 raw_image = convert_image(vbox_vm_info(image_id))
210 volume = convert_volume(vbox_get_volume(raw_image['ide controller-imageuuid-0-0'.to_sym]))
211 vms << Instance.new({
212 :id => raw_image[:uuid],
213 :image_id => volume ? raw_image[:uuid] : '',
214 :name => raw_image[:name],
215 :state => convert_state(raw_image[:vmstate], volume),
216 :owner_id => ENV['USER'] || ENV['USERNAME'] || 'nobody',
217 :realm_id => 'local',
218 :public_addresses => vbox_get_ip(raw_image[:uuid]),
219 :private_addresses => vbox_get_ip(raw_image[:uuid]),
220 :actions => instance_actions_for(convert_state(raw_image[:vmstate], volume))
226 # Warning: You need VirtualHost guest additions for this
227 def vbox_get_ip(uuid)
228 raw_ip = vbox_client("guestproperty get #{uuid} /VirtualBox/GuestInfo/Net/0/V4/IP")
229 raw_ip = raw_ip.split(':').last.strip
230 if raw_ip.eql?('No value set!')
237 def vbox_get_volume(uuid)
238 vbox_client("showhdinfo #{uuid}")
241 def convert_state(state, volume)
242 volume.nil? ? 'PENDING' : state.strip.upcase
245 def convert_image(image)
247 image.split("\n").each do |i|
249 img[:"#{si.first.gsub('"', '').strip.downcase}"] = si.last.strip.gsub('"', '')
254 def instance_volume_location(instance_id)
255 volume_uuid = convert_image(vbox_vm_info(instance_id))['ide controller-imageuuid-0-0'.to_sym]
256 convert_raw_volume(vbox_get_volume(volume_uuid))[:location]
259 def convert_raw_volume(volume)
261 volume.split("\n").each do |v|
264 vol[:"#{v.first.strip.downcase.gsub(/\W/, '-')}"] = v.last.strip
269 def convert_volume(volume)
270 vol = convert_raw_volume(volume)
271 return nil unless vol[:uuid]
274 :created => Time.now,
275 :state => 'AVAILABLE',
276 :capacity => vol["logical-size".to_sym],
277 :instance_id => vol["in-use-by-vms".to_sym],
278 :device => vol[:type]
282 def convert_images(images)
284 images.split("\n").each do |image|
285 image_id = image.match(/^\"(.+)\" \{(.+)\}$/).to_a.last
286 raw_image = convert_image(vbox_vm_info(image_id))
287 volume = convert_volume(vbox_get_volume(raw_image['ide controller-imageuuid-0-0'.to_sym]))
289 capacity = ", #{volume.capacity} HDD"
291 :id => raw_image[:uuid],
292 :name => raw_image[:name],
293 :description => "#{raw_image[:memory]} MB RAM, #{raw_image[:cpu] || 1} CPU#{capacity}",
294 :owner_id => ENV['USER'] || ENV['USERNAME'] || 'nobody',
295 :architecture => `uname -m`.strip