2 require 'rexml/document'
6 # These class attributes will be lazily initialized during the first
8 # This is the libvirt connection, of which we only want one and
9 # which can persist for different VM instances (even in parallel)
11 # This is a storage helper that deals with volume manipulation. The
12 # storage it deals with persists across VMs, by necessity.
23 attr_reader :domain, :display, :ip, :mac, :net
25 def initialize(xml_path, x_display)
26 @@virt ||= Libvirt::open("qemu:///system")
28 default_domain_xml = File.read("#{@xml_path}/default.xml")
29 update_domain(default_domain_xml)
30 default_net_xml = File.read("#{@xml_path}/default_net.xml")
31 update_net(default_net_xml)
32 @display = Display.new(@domain_name, x_display)
33 set_cdrom_boot($tails_iso)
35 # unlike the domain and net the storage pool should survive VM
36 # teardown (so a new instance can use e.g. a previously created
37 # USB drive), so we only create a new one if there is none.
38 @@storage ||= VMStorage.new(@@virt, xml_path)
45 def update_domain(xml)
46 domain_xml = REXML::Document.new(xml)
47 @domain_name = domain_xml.elements['domain/name'].text
49 @domain = @@virt.define_domain_xml(xml)
53 net_xml = REXML::Document.new(xml)
54 @net_name = net_xml.elements['network/name'].text
55 @ip = net_xml.elements['network/ip/dhcp/host/'].attributes['ip']
56 @mac = net_xml.elements['network/ip/dhcp/host/'].attributes['mac']
58 @net = @@virt.define_network_xml(xml)
64 domain = @@virt.lookup_domain_by_name(@domain_name)
65 domain.destroy if domain.active?
73 net = @@virt.lookup_network_by_name(@net_name)
74 net.destroy if net.active?
80 def set_network_link_state(state)
81 domain_xml = REXML::Document.new(@domain.xml_desc)
82 domain_xml.elements['domain/devices/interface/link'].attributes['state'] = state
84 @domain.update_device(domain_xml.elements['domain/devices/interface'].to_s)
86 update_domain(domain_xml.to_s)
91 set_network_link_state('up')
95 set_network_link_state('down')
98 def set_cdrom_tray_state(state)
99 domain_xml = REXML::Document.new(@domain.xml_desc)
100 domain_xml.elements.each('domain/devices/disk') do |e|
101 if e.attribute('device').to_s == "cdrom"
102 e.elements['target'].attributes['tray'] = state
104 @domain.update_device(e.to_s)
106 update_domain(domain_xml.to_s)
113 set_cdrom_tray_state('open')
117 set_cdrom_tray_state('closed')
120 def set_boot_device(dev)
122 raise "boot settings can only be set for inactive vms"
124 domain_xml = REXML::Document.new(@domain.xml_desc)
125 domain_xml.elements['domain/os/boot'].attributes['dev'] = dev
126 update_domain(domain_xml.to_s)
129 def set_cdrom_image(image)
130 domain_xml = REXML::Document.new(@domain.xml_desc)
131 domain_xml.elements.each('domain/devices/disk') do |e|
132 if e.attribute('device').to_s == "cdrom"
133 if ! e.elements['source']
134 e.add_element('source')
136 e.elements['source'].attributes['file'] = image
138 @domain.update_device(e.to_s, Libvirt::Domain::DEVICE_MODIFY_FORCE)
140 update_domain(domain_xml.to_s)
150 def set_cdrom_boot(image)
152 raise "boot settings can only be set for inactice vms"
154 set_boot_device('cdrom')
155 set_cdrom_image(image)
159 def plug_drive(name, type)
160 # Get the next free /dev/sdX on guest
162 domain_xml = REXML::Document.new(@domain.xml_desc)
163 domain_xml.elements.each('domain/devices/disk/target') do |e|
164 used_devs <<= e.attribute('dev').to_s
168 while used_devs.include? dev
169 letter = (letter[0].ord + 1).chr
174 xml = REXML::Document.new(File.read("#{@xml_path}/disk.xml"))
175 xml.elements['disk/source'].attributes['file'] = @@storage.disk_path(name)
176 xml.elements['disk/driver'].attributes['type'] = @@storage.disk_format(name)
177 xml.elements['disk/target'].attributes['dev'] = dev
178 xml.elements['disk/target'].attributes['bus'] = type
180 xml.elements['disk/target'].attributes['removable'] = 'on'
184 @domain.attach_device(xml.to_s)
186 domain_xml = REXML::Document.new(@domain.xml_desc)
187 domain_xml.elements['domain/devices'].add_element(xml)
188 update_domain(domain_xml.to_s)
192 def disk_xml_desc(name)
193 domain_xml = REXML::Document.new(@domain.xml_desc)
194 domain_xml.elements.each('domain/devices/disk') do |e|
196 if e.elements['source'].attribute('file').to_s == @@storage.disk_path(name)
206 def unplug_drive(name)
207 xml = disk_xml_desc(name)
208 @domain.detach_device(xml)
212 xml = REXML::Document.new(disk_xml_desc(name))
213 return "/dev/" + xml.elements['disk/target'].attribute('dev').to_s
216 def disk_detected?(name)
217 return execute("test -b #{disk_dev(name)}").success?
220 def set_disk_boot(name, type)
222 raise "boot settings can only be set for inactive vms"
224 plug_drive(name, type)
225 set_boot_device('hd')
226 # For some reason setting the boot device doesn't prevent cdrom
227 # boot unless it's empty
231 # XXX-9p: Shares don't work together with snapshot save+restore. See
232 # XXX-9p in common_steps.rb for more information.
233 def add_share(source, tag)
235 raise "shares can only be added to inactice vms"
237 xml = REXML::Document.new(File.read("#{@xml_path}/fs_share.xml"))
238 xml.elements['filesystem/source'].attributes['dir'] = source
239 xml.elements['filesystem/target'].attributes['dir'] = tag
240 domain_xml = REXML::Document.new(@domain.xml_desc)
241 domain_xml.elements['domain/devices'].add_element(xml)
242 update_domain(domain_xml.to_s)
247 domain_xml = REXML::Document.new(@domain.xml_desc)
248 domain_xml.elements.each('domain/devices/filesystem') do |e|
249 list << e.elements['target'].attribute('dir').to_s
254 def set_ram_size(size, unit = "KiB")
255 raise "System memory can only be added to inactice vms" if is_running?
256 domain_xml = REXML::Document.new(@domain.xml_desc)
257 domain_xml.elements['domain/memory'].text = size
258 domain_xml.elements['domain/memory'].attributes['unit'] = unit
259 domain_xml.elements['domain/currentMemory'].text = size
260 domain_xml.elements['domain/currentMemory'].attributes['unit'] = unit
261 update_domain(domain_xml.to_s)
264 def get_ram_size_in_bytes
265 domain_xml = REXML::Document.new(@domain.xml_desc)
266 unit = domain_xml.elements['domain/memory'].attribute('unit').to_s
267 size = domain_xml.elements['domain/memory'].text.to_i
268 return convert_to_bytes(size, unit)
272 raise "System architecture can only be set to inactice vms" if is_running?
273 domain_xml = REXML::Document.new(@domain.xml_desc)
274 domain_xml.elements['domain/os/type'].attributes['arch'] = arch
275 update_domain(domain_xml.to_s)
278 def add_hypervisor_feature(feature)
279 raise "Hypervisor features can only be added to inactice vms" if is_running?
280 domain_xml = REXML::Document.new(@domain.xml_desc)
281 domain_xml.elements['domain/features'].add_element(feature)
282 update_domain(domain_xml.to_s)
285 def drop_hypervisor_feature(feature)
286 raise "Hypervisor features can only be fropped from inactice vms" if is_running?
287 domain_xml = REXML::Document.new(@domain.xml_desc)
288 domain_xml.elements['domain/features'].delete_element(feature)
289 update_domain(domain_xml.to_s)
292 def disable_pae_workaround
293 # add_hypervisor_feature("nonpae") results in a libvirt error, and
294 # drop_hypervisor_feature("pae") alone won't disable pae. Hence we
295 # use this workaround.
297 <qemu:commandline xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
298 <qemu:arg value='-cpu'/>
299 <qemu:arg value='pentium,-pae'/>
302 domain_xml = REXML::Document.new(@domain.xml_desc)
303 domain_xml.elements['domain'].add_element(REXML::Document.new(xml))
304 update_domain(domain_xml.to_s)
309 return @domain.active?
315 def execute(cmd, user = "root")
316 return VMCommand.new(self, cmd, { :user => user, :spawn => false })
319 def spawn(cmd, user = "root")
320 return VMCommand.new(self, cmd, { :user => user, :spawn => true })
323 def wait_until_remote_shell_is_up(timeout = 30)
324 VMCommand.wait_until_remote_shell_is_up(self, timeout)
327 def host_to_guest_time_sync
328 host_time= DateTime.now.strftime("%s").to_s
329 execute("date -s '@#{host_time}'").success?
333 return execute("/sbin/ifconfig eth0 | grep -q 'inet addr'").success?
336 def has_process?(process)
337 return execute("pidof " + process).success?
340 def save_snapshot(path)
345 def restore_snapshot(path)
346 # Clean up current domain so its snapshot can be restored
348 Libvirt::Domain::restore(@@virt, path)
349 @domain = @@virt.lookup_domain_by_name(@domain_name)
354 return if is_running?
360 @domain.destroy if is_running?
370 def take_screenshot(description)
371 @display.take_screenshot(description)
374 def get_remote_shell_port
375 domain_xml = REXML::Document.new(@domain.xml_desc)
376 domain_xml.elements.each('domain/devices/serial') do |e|
377 if e.attribute('type').to_s == "tcp"
378 return e.elements['source'].attribute('service').to_s.to_i