Initial import
[deltacloud-devel:deltacloud-virtualbox-driver.git] / lib / deltacloud / drivers / vbox / vbox_driver.rb
1 #
2 # Copyright (C) 2009  Red Hat, Inc.
3 #
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.
8 #
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.
13 #
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
17
18 # INSTALLATION:
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 ;-)
23
24 require 'deltacloud/base_driver'
25
26 module Deltacloud
27   module Drivers
28     module Vbox
29       class VboxDriver < Deltacloud::BaseDriver
30
31         VBOX_MANAGE_PATH = '/usr/bin'
32
33         FLAVORS = [
34           Flavor.new({
35             :id => 'small',
36             :memory => 0.5,
37             :storage => 1,
38             :architecture => `uname -m`
39           }),
40           Flavor.new({
41             :id => 'medium',
42             :memory => 1,
43             :storage => 1,
44             :architecture => `uname -m`
45           }),
46           Flavor.new({
47             :id => 'large',
48             :memory => 2,
49             :storage => 1,
50             :architecture => `uname -m`
51           }),
52           Flavor.new({
53             :id => 'microsoft',
54             :memory => 1,
55             :storage => 1,
56             :architecture => `uname -m`
57           }),
58         ]
59
60         REALMS = [
61           Realm.new({
62             :id => 'local',
63             :name => 'localhost',
64             :limit => 100,
65             :state => 'AVAILABLE'
66           })
67         ]
68
69         define_hardware_profile 'small' do
70           cpu           1
71           memory        0.5
72           storage       1
73           architecture  `uname -m`.strip
74         end
75         
76         define_hardware_profile 'medium' do
77           cpu           1
78           memory        1
79           storage       1
80           architecture  `uname -m`.strip
81         end
82
83         define_hardware_profile 'large' do
84           cpu           2
85           memory        2
86           storage       1
87           architecture  `uname -m`.strip
88         end
89
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)
99         end
100
101         def flavors(credentials, opts = nil)
102           return FLAVORS
103         end
104
105         def realms(credentials, opts = nil)
106           return REALMS
107         end
108
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]}
114         end
115
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 )
121           instances
122         end
123
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}"
127
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
131           
132           # Add Hardware profile to this machine
133           memory = 0.5
134           ostype = "Linux"
135           cpu = '1'
136
137           if opts[:flavor_id]
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}"
142           end
143
144           memory = ((memory*1.024)*1000).to_i
145
146           vbox_client("modifyvm '#{new_uid}' --ostype #{ostype} --memory #{memory} --vram 16 --nic1 bridged --bridgeadapter eth0 --cableconnected1 on --cpus #{cpu}")
147
148           # Add storage
149           # This will 'reuse' existing image
150           location = instance_volume_location(instance.id)
151           new_location = File.join(File.dirname(location), name+'.vdi')
152
153           # This need to be in fork, because it takes some time with large images
154           fork do
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}'")
158           end
159         end
160
161         def reboot_instance(credentials, id)
162           vbox_client("controlvm '#{id}' reset")
163         end
164
165         def stop_instance(credentials, id)
166           vbox_client("controlvm '#{id}' pause")
167         end
168
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}'")
174           else
175             vbox_client("controlvm '#{id}' resume")
176           end
177         end
178
179         def destroy_instance(credentials, id)
180           vbox_client("controlvm '#{id}' poweroff")
181         end
182
183         def storage_volumes(credentials, opts = nil)
184           volumes = []
185           require 'pp'
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]
189             next unless hdd_id
190             volumes << convert_volume(vbox_client("showhdinfo '#{hdd_id}'"))
191           end
192           filter_on( volumes, :id, opts )
193         end
194
195         private
196
197         def vbox_client(cmd)
198           `#{VBOX_MANAGE_PATH}/VBoxManage -q #{cmd}`.strip
199         end
200
201         def vbox_vm_info(uid)
202           vbox_client("showvminfo --machinereadable '#{uid}'")
203         end
204
205         def convert_instances(instances)
206           vms = []
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))
221             })
222           end
223           return vms
224         end
225
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!')
231             return []
232           else
233             return [raw_ip]
234           end
235         end
236
237         def vbox_get_volume(uuid)
238           vbox_client("showhdinfo #{uuid}")
239         end
240
241         def convert_state(state, volume)
242           volume.nil? ? 'PENDING' : state.strip.upcase
243         end
244
245         def convert_image(image)
246           img = {}
247           image.split("\n").each do |i|
248             si = i.split('=')
249             img[:"#{si.first.gsub('"', '').strip.downcase}"] = si.last.strip.gsub('"', '')
250           end
251           return img
252         end
253
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]
257         end
258
259         def convert_raw_volume(volume)
260           vol = {}
261           volume.split("\n").each do |v|
262             v = v.split(':')
263             next unless v.first
264             vol[:"#{v.first.strip.downcase.gsub(/\W/, '-')}"] = v.last.strip
265           end
266           return vol
267         end
268
269         def convert_volume(volume)
270           vol = convert_raw_volume(volume)
271           return nil unless vol[:uuid]
272           StorageVolume.new(
273             :id => 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]
279           )
280         end
281
282         def convert_images(images)
283           vms = []
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]))
288             next unless volume
289             capacity = ", #{volume.capacity} HDD"
290             vms << Image.new(
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
296             )
297           end
298           return vms
299         end
300
301       end
302     end
303   end
304 end