vm_helper.rb 20.2 KB
Newer Older
1
require 'ipaddr'
2
require 'libvirt'
3
require 'rexml/document'
4

kytv's avatar
kytv committed
5
class ExecutionFailedInVM < StandardError
6
7
end

8
9
class VMNet

10
  attr_reader :net_name, :net
11
12
13

  def initialize(virt, xml_path)
    @virt = virt
14
    @net_name = LIBVIRT_NETWORK_NAME
15
    net_xml = File.read("#{xml_path}/default_net.xml")
16
17
18
19
    rexml = REXML::Document.new(net_xml)
    rexml.elements['network'].add_element('name')
    rexml.elements['network/name'].text = @net_name
    rexml.elements['network'].add_element('uuid')
20
    rexml.elements['network/uuid'].text = LIBVIRT_NETWORK_UUID
21
    update(rexml.to_s)
22
  rescue Exception => e
23
    destroy_and_undefine
24
25
26
    raise e
  end

27
28
29
  # We lookup by name so we also catch networks from previous test
  # suite runs that weren't properly cleaned up (e.g. aborted).
  def destroy_and_undefine
30
    begin
Tails developers's avatar
Tails developers committed
31
32
33
      old_net = @virt.lookup_network_by_name(@net_name)
      old_net.destroy if old_net.active?
      old_net.undefine
34
35
36
37
38
    rescue
    end
  end

  def update(xml)
39
    destroy_and_undefine
40
41
42
43
    @net = @virt.define_network_xml(xml)
    @net.create
  end

44
45
46
47
  def bridge_name
    @net.bridge_name
  end

48
49
  def bridge_ip_addr
    net_xml = REXML::Document.new(@net.xml_desc)
anonym's avatar
anonym committed
50
    IPAddr.new(net_xml.elements['network/ip'].attributes['address']).to_s
51
52
  end

anonym's avatar
anonym committed
53
  def guest_real_mac
anonym's avatar
anonym committed
54
55
56
57
    net_xml = REXML::Document.new(@net.xml_desc)
    net_xml.elements['network/ip/dhcp/host/'].attributes['mac']
  end

58
  def bridge_mac
59
    File.open("/sys/class/net/#{bridge_name}/address", "rb").read.chomp
60
61
62
63
  end
end


64
65
class VM

66
  attr_reader :domain, :domain_name, :display, :vmnet, :storage
67

68
  def initialize(virt, xml_path, vmnet, storage, x_display)
69
    @virt = virt
70
    @xml_path = xml_path
71
    @vmnet = vmnet
72
    @storage = storage
73
    @domain_name = LIBVIRT_DOMAIN_NAME
74
    default_domain_xml = File.read("#{@xml_path}/default.xml")
75
76
77
78
    rexml = REXML::Document.new(default_domain_xml)
    rexml.elements['domain'].add_element('name')
    rexml.elements['domain/name'].text = @domain_name
    rexml.elements['domain'].add_element('uuid')
79
    rexml.elements['domain/uuid'].text = LIBVIRT_DOMAIN_UUID
80
    update(rexml.to_s)
81
    @display = Display.new(@domain_name, x_display)
82
    set_cdrom_boot(TAILS_ISO)
83
    plug_network
84
  rescue Exception => e
85
    destroy_and_undefine
86
    raise e
87
88
  end

89
  def update(xml)
90
    destroy_and_undefine
91
    @domain = @virt.define_domain_xml(xml)
92
93
  end

94
95
96
  # We lookup by name so we also catch domains from previous test
  # suite runs that weren't properly cleaned up (e.g. aborted).
  def destroy_and_undefine
97
    @display.stop if @display && @display.active?
98
    begin
Tails developers's avatar
Tails developers committed
99
100
101
      old_domain = @virt.lookup_domain_by_name(@domain_name)
      old_domain.destroy if old_domain.active?
      old_domain.undefine
102
    rescue
103
    end
104
105
  end

106
  def real_mac
anonym's avatar
anonym committed
107
    @vmnet.guest_real_mac
108
109
  end

110
111
112
113
114
115
116
117
118
119
120
121
122
  def set_hardware_clock(time)
    assert(not(is_running?), 'The hardware clock cannot be set when the ' +
                             'VM is running')
    assert(time.instance_of?(Time), "Argument must be of type 'Time'")
    adjustment = (time - Time.now).to_i
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    clock_rexml_element = domain_rexml.elements['domain'].add_element('clock')
    clock_rexml_element.add_attributes('offset' => 'variable',
                                       'basis' => 'utc',
                                       'adjustment' => adjustment.to_s)
    update(domain_rexml.to_s)
  end

123
124
125
126
127
128
  def set_network_link_state(state)
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements['domain/devices/interface/link'].attributes['state'] = state
    if is_running?
      @domain.update_device(domain_xml.elements['domain/devices/interface'].to_s)
    else
129
      update(domain_xml.to_s)
130
    end
131
132
  end

133
  def plug_network
134
    set_network_link_state('up')
135
136
137
  end

  def unplug_network
138
    set_network_link_state('down')
139
140
  end

141
142
143
144
145
146
  def set_boot_device(dev)
    if is_running?
      raise "boot settings can only be set for inactive vms"
    end
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements['domain/os/boot'].attributes['dev'] = dev
147
    update(domain_xml.to_s)
148
149
  end

anonym's avatar
anonym committed
150
  def add_cdrom_device
151
    if is_running?
152
      raise "Can't attach a CDROM device to a running domain"
153
    end
anonym's avatar
anonym committed
154
155
156
157
158
159
160
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    if domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
      raise "A CDROM device already exists"
    end
    cdrom_rexml = REXML::Document.new(File.read("#{@xml_path}/cdrom.xml")).root
    domain_rexml.elements['domain/devices'].add_element(cdrom_rexml)
    update(domain_rexml.to_s)
161
162
  end

anonym's avatar
anonym committed
163
  def remove_cdrom_device
164
    if is_running?
anonym's avatar
anonym committed
165
      raise "Can't detach a CDROM device to a running domain"
166
    end
anonym's avatar
anonym committed
167
168
169
170
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
    if cdrom_el.nil?
      raise "No CDROM device is present"
171
    end
anonym's avatar
anonym committed
172
173
    domain_rexml.elements["domain/devices"].delete_element(cdrom_el)
    update(domain_rexml.to_s)
174
175
  end

anonym's avatar
anonym committed
176
177
178
179
180
181
182
183
  def eject_cdrom
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
    if cdrom_el.nil?
      raise "No CDROM device is present"
    end
    cdrom_el.delete_element('source')
    update(domain_rexml.to_s)
184
185
186
  rescue Libvirt::Error => e
    # While the CD-ROM is removed successfully we still get this
    # error, so let's ignore it.
187
188
189
190
    acceptable_error =
      "Call to virDomainUpdateDeviceFlags failed: internal error: unable to " +
      "execute QEMU command 'eject': (Tray of device '.*' is not open|" +
      "Device '.*' is locked)"
191
    raise e if not(Regexp.new(acceptable_error).match(e.to_s))
192
193
  end

anonym's avatar
anonym committed
194
195
196
197
198
199
200
201
202
203
204
  def set_cdrom_image(image)
    if image.nil? or image == ''
      raise "Can't set cdrom image to an empty string"
    end
    eject_cdrom
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    cdrom_el = domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
    cdrom_el.add_element('source', { 'file' => image })
    update(domain_rexml.to_s)
  end

205
  def set_cdrom_boot(image)
206
    if is_running?
kytv's avatar
kytv committed
207
      raise "boot settings can only be set for inactive vms"
208
    end
anonym's avatar
anonym committed
209
210
211
212
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    if not domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
      add_cdrom_device
    end
213
    set_cdrom_image(image)
214
    set_boot_device('cdrom')
215
216
  end

217
218
219
220
221
222
223
224
225
  def list_disk_devs
    ret = []
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/disk') do |e|
      ret << e.elements['target'].attribute('dev').to_s
    end
    return ret
  end

226
  def plug_drive(name, type)
anonym's avatar
anonym committed
227
228
229
    if disk_plugged?(name)
      raise "disk '#{name}' already plugged"
    end
230
231
232
233
234
235
236
237
238
    removable_usb = nil
    case type
    when "removable usb", "usb"
      type = "usb"
      removable_usb = "on"
    when "non-removable usb"
      type = "usb"
      removable_usb = "off"
    end
239
240
241
    # Get the next free /dev/sdX on guest
    letter = 'a'
    dev = "sd" + letter
242
    while list_disk_devs.include?(dev)
243
244
245
246
247
      letter = (letter[0].ord + 1).chr
      dev = "sd" + letter
    end
    assert letter <= 'z'

248
    xml = REXML::Document.new(File.read("#{@xml_path}/disk.xml"))
249
250
    xml.elements['disk/source'].attributes['file'] = @storage.disk_path(name)
    xml.elements['disk/driver'].attributes['type'] = @storage.disk_format(name)
251
    xml.elements['disk/target'].attributes['dev'] = dev
252
    xml.elements['disk/target'].attributes['bus'] = type
253
    xml.elements['disk/target'].attributes['removable'] = removable_usb if removable_usb
254
255
256
257
258
259

    if is_running?
      @domain.attach_device(xml.to_s)
    else
      domain_xml = REXML::Document.new(@domain.xml_desc)
      domain_xml.elements['domain/devices'].add_element(xml)
260
      update(domain_xml.to_s)
261
    end
262
263
  end

264
  def disk_xml_desc(name)
265
266
267
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/disk') do |e|
      begin
268
        if e.elements['source'].attribute('file').to_s == @storage.disk_path(name)
269
270
271
272
273
274
275
276
277
          return e.to_s
        end
      rescue
        next
      end
    end
    return nil
  end

anonym's avatar
anonym committed
278
279
280
281
282
283
284
285
286
  def disk_rexml_desc(name)
    xml = disk_xml_desc(name)
    if xml
      return REXML::Document.new(xml)
    else
      return nil
    end
  end

287
288
  def unplug_drive(name)
    xml = disk_xml_desc(name)
289
290
291
    @domain.detach_device(xml)
  end

292
293
294
295
296
297
298
299
300
301
  def disk_type(dev)
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/disk') do |e|
      if e.elements['target'].attribute('dev').to_s == dev
        return e.elements['driver'].attribute('type').to_s
      end
    end
    raise "No such disk device '#{dev}'"
  end

302
  def disk_dev(name)
anonym's avatar
anonym committed
303
304
    rexml = disk_rexml_desc(name) or return nil
    return "/dev/" + rexml.elements['disk/target'].attribute('dev').to_s
305
306
  end

anonym's avatar
anonym committed
307
308
309
310
311
312
313
314
315
316
317
  def disk_name(dev)
    dev = File.basename(dev)
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/disk') do |e|
      if /^#{e.elements['target'].attribute('dev').to_s}/.match(dev)
        return File.basename(e.elements['source'].attribute('file').to_s)
      end
    end
    raise "No such disk device '#{dev}'"
  end

318
319
320
321
  def udisks_disk_dev(name)
    return disk_dev(name).gsub('/dev/', '/org/freedesktop/UDisks/devices/')
  end

322
  def disk_detected?(name)
anonym's avatar
anonym committed
323
324
    dev = disk_dev(name) or return false
    return execute("test -b #{dev}").success?
325
326
  end

anonym's avatar
anonym committed
327
328
  def disk_plugged?(name)
    return not(disk_xml_desc(name).nil?)
329
330
  end

331
  def set_disk_boot(name, type)
332
333
334
    if is_running?
      raise "boot settings can only be set for inactive vms"
    end
anonym's avatar
anonym committed
335
    plug_drive(name, type) if not(disk_plugged?(name))
336
    set_boot_device('hd')
anonym's avatar
anonym committed
337
    # XXX:Stretch: since our isotesters upgraded QEMU from
338
339
    # 2.5+dfsg-4~bpo8+1 to 2.6+dfsg-3.1~bpo8+1 it seems we must remove
    # the CDROM device to allow disk boot. This is not the case with the same
anonym's avatar
anonym committed
340
341
342
    # version on Debian Sid. Let's hope we can remove this ugly
    # workaround when we only support running the automated test suite
    # on Stretch.
343
344
345
346
    domain_rexml = REXML::Document.new(@domain.xml_desc)
    if domain_rexml.elements["domain/devices/disk[@device='cdrom']"]
      remove_cdrom_device
    end
347
348
  end

349
350
  # XXX-9p: Shares don't work together with snapshot save+restore. See
  # XXX-9p in common_steps.rb for more information.
351
352
  def add_share(source, tag)
    if is_running?
kytv's avatar
kytv committed
353
      raise "shares can only be added to inactive vms"
354
    end
355
    # The complete source directory must be group readable by the user
356
357
    # running the virtual machine, and world readable so the user inside
    # the VM can access it (since we use the passthrough security model).
358
    FileUtils.chown_R(nil, "libvirt-qemu", source)
359
    FileUtils.chmod_R("go+rX", source)
360
    xml = REXML::Document.new(File.read("#{@xml_path}/fs_share.xml"))
361
362
363
364
    xml.elements['filesystem/source'].attributes['dir'] = source
    xml.elements['filesystem/target'].attributes['dir'] = tag
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements['domain/devices'].add_element(xml)
365
    update(domain_xml.to_s)
366
367
  end

368
369
370
371
372
373
374
375
376
  def list_shares
    list = []
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/filesystem') do |e|
      list << e.elements['target'].attribute('dir').to_s
    end
    return list
  end

377
  def set_ram_size(size, unit = "KiB")
kytv's avatar
kytv committed
378
    raise "System memory can only be added to inactive vms" if is_running?
379
380
381
382
383
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements['domain/memory'].text = size
    domain_xml.elements['domain/memory'].attributes['unit'] = unit
    domain_xml.elements['domain/currentMemory'].text = size
    domain_xml.elements['domain/currentMemory'].attributes['unit'] = unit
384
    update(domain_xml.to_s)
385
386
387
388
389
390
391
392
393
  end

  def get_ram_size_in_bytes
    domain_xml = REXML::Document.new(@domain.xml_desc)
    unit = domain_xml.elements['domain/memory'].attribute('unit').to_s
    size = domain_xml.elements['domain/memory'].text.to_i
    return convert_to_bytes(size, unit)
  end

394
395
  def set_os_loader(type)
    if is_running?
kytv's avatar
kytv committed
396
      raise "boot settings can only be set for inactive vms"
397
398
399
400
401
402
    end
    if type == 'UEFI'
      domain_xml = REXML::Document.new(@domain.xml_desc)
      domain_xml.elements['domain/os'].add_element(REXML::Document.new(
        '<loader>/usr/share/ovmf/OVMF.fd</loader>'
      ))
403
      update(domain_xml.to_s)
404
405
406
407
408
    else
      raise "unsupported OS loader type"
    end
  end

409
  def is_running?
410
411
412
413
414
    begin
      return @domain.active?
    rescue
      return false
    end
415
416
  end

anonym's avatar
anonym committed
417
418
  def execute(cmd, options = {})
    options[:user] ||= "root"
419
    options[:spawn] = false unless options.has_key?(:spawn)
420
421
422
    if options[:libs]
      libs = options[:libs]
      options.delete(:libs)
anonym's avatar
anonym committed
423
      libs = [libs] if not(libs.methods.include? :map)
424
425
426
427
428
429
      cmds = libs.map do |lib_name|
        ". /usr/local/lib/tails-shell-library/#{lib_name}.sh"
      end
      cmds << cmd
      cmd = cmds.join(" && ")
    end
430
    return RemoteShell::ShellCommand.new(self, cmd, options)
431
432
  end

anonym's avatar
anonym committed
433
434
  def execute_successfully(*args)
    p = execute(*args)
435
436
437
    begin
      assert_vmcommand_success(p)
    rescue Test::Unit::AssertionFailedError => e
438
      raise ExecutionFailedInVM.new(e)
439
    end
440
441
442
    return p
  end

anonym's avatar
anonym committed
443
444
445
  def spawn(cmd, options = {})
    options[:spawn] = true
    return execute(cmd, options)
446
447
  end

448
  def wait_until_remote_shell_is_up(timeout = 90)
anonym's avatar
anonym committed
449
450
451
    msg = 'hello?'
    try_for(timeout, :msg => "Remote shell seems to be down") do
      Timeout::timeout(3) do
anonym's avatar
anonym committed
452
        execute_successfully("echo '#{msg}'").stdout.chomp == msg
anonym's avatar
anonym committed
453
454
      end
    end
455
456
  end

457
458
  def host_to_guest_time_sync
    host_time= DateTime.now.strftime("%s").to_s
459
    execute("date -s '@#{host_time}'").success?
460
461
  end

462
463
464
465
466
  def has_network?
    return execute("/sbin/ifconfig eth0 | grep -q 'inet addr'").success?
  end

  def has_process?(process)
467
    return execute("pidof -x -o '%PPID' " + process).success?
468
469
  end

470
471
472
473
  def pidof(process)
    return execute("pidof -x -o '%PPID' " + process).stdout.chomp.split
  end

474
475
476
  def select_virtual_desktop(desktop_number, user = LIVE_USER)
    assert(desktop_number >= 0 && desktop_number <=3,
           "Only values between 0 and 3 are valid virtual desktop numbers")
477
    execute_successfully(
478
      "xdotool set_desktop '#{desktop_number}'",
anonym's avatar
anonym committed
479
      :user => user
480
481
482
    )
  end

Tails developers's avatar
Tails developers committed
483
  def focus_window(window_title, user = LIVE_USER)
484
485
    def do_focus(window_title, user)
      execute_successfully(
anonym's avatar
anonym committed
486
487
        "xdotool search --name '#{window_title}' windowactivate --sync",
        :user => user
488
489
490
491
492
493
      )
    end

    begin
      do_focus(window_title, user)
    rescue ExecutionFailedInVM
intrigeri's avatar
intrigeri committed
494
      # Often when xdotool fails to focus a window it'll work when retried
495
496
497
498
      # after redrawing the screen.  Switching to a new virtual desktop then
      # back seems to be a reliable way to handle this.
      select_virtual_desktop(3)
      select_virtual_desktop(0)
499
      sleep 5 # there aren't any visual indicators which can be used here
500
501
      do_focus(window_title, user)
    end
502
503
  end

504
  def file_exist?(file)
505
    execute("test -e '#{file}'").success?
506
507
  end

508
509
  def directory_exist?(directory)
    execute("test -d '#{directory}'").success?
510
511
  end

512
513
  def file_open(path)
    f = RemoteShell::File.new(self, path)
anonym's avatar
anonym committed
514
    yield f if block_given?
515
    return f
516
517
  end

518
  def file_content(path)
519
    file_open(path) { |f| return f.read() }
520
521
522
  end

  def file_overwrite(path, lines)
523
    lines = lines.join("\n") if lines.class == Array
524
    file_open(path) { |f| return f.write(lines) }
525
526
  end

527
528
  def file_append(path, lines)
    lines = lines.join("\n") if lines.class == Array
529
    file_open(path) { |f| return f.append(lines) }
530
531
  end

532
  def set_clipboard(text)
anonym's avatar
anonym committed
533
    execute_successfully("echo -n '#{text}' | xsel --input --clipboard",
534
535
536
                         :user => LIVE_USER)
  end

anonym's avatar
anonym committed
537
538
539
540
  def get_clipboard
    execute_successfully("xsel --output --clipboard", :user => LIVE_USER).stdout
  end

541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
  def internal_snapshot_xml(name)
    disk_devs = list_disk_devs
    disks_xml = "    <disks>\n"
    for dev in disk_devs
      snapshot_type = disk_type(dev) == "qcow2" ? 'internal' : 'no'
      disks_xml +=
        "      <disk name='#{dev}' snapshot='#{snapshot_type}'></disk>\n"
    end
    disks_xml += "    </disks>"
    return <<-EOF
<domainsnapshot>
  <name>#{name}</name>
  <description>Snapshot for #{name}</description>
#{disks_xml}
  </domainsnapshot>
EOF
557
558
  end

559
  def VM.ram_only_snapshot_path(name)
560
    return "#{$config["TMPDIR"]}/#{name}-snapshot.memstate"
561
562
563
564
565
566
  end

  def save_snapshot(name)
    # If we have no qcow2 disk device, we'll use "memory state"
    # snapshots, and if we have at least one qcow2 disk device, we'll
    # use internal "system checkpoint" (memory + disks) snapshots. We
anonym's avatar
anonym committed
567
    # have to do this since internal snapshots don't work when no
568
    # such disk is available. We can do this with external snapshots,
anonym's avatar
anonym committed
569
    # which are better in many ways, but libvirt doesn't know how to
570
571
572
573
574
575
576
577
578
579
580
581
582
    # restore (revert back to) them yet.
    # WARNING: If only transient disks, i.e. disks that were plugged
    # after starting the domain, are used then the memory state will
    # be dropped. External snapshots would also fix this.
    internal_snapshot = false
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/disk') do |e|
      if e.elements['driver'].attribute('type').to_s == "qcow2"
        internal_snapshot = true
        break
      end
    end

anonym's avatar
anonym committed
583
584
585
    # Note: In this case the "opposite" of `internal_snapshot` is not
    # anything relating to external snapshots, but actually "memory
    # state"(-only) snapshots.
586
587
588
589
    if internal_snapshot
      xml = internal_snapshot_xml(name)
      @domain.snapshot_create_xml(xml)
    else
590
      snapshot_path = VM.ram_only_snapshot_path(name)
591
592
593
594
595
596
597
598
599
600
601
602
603
      @domain.save(snapshot_path)
      # For consistency with the internal snapshot case (which is
      # "live", so the domain doesn't go down) we immediately restore
      # the snapshot.
      # Assumption: that *immediate* save + restore doesn't mess up
      # with network state and similar, and is fast enough to not make
      # the clock drift too much.
      restore_snapshot(name)
    end
  end

  def restore_snapshot(name)
    @domain.destroy if is_running?
604
    @display.stop if @display and @display.active?
605
606
    # See comment in save_snapshot() for details on why we use two
    # different type of snapshots.
607
    potential_ram_only_snapshot_path = VM.ram_only_snapshot_path(name)
anonym's avatar
anonym committed
608
609
    if File.exist?(potential_ram_only_snapshot_path)
      Libvirt::Domain::restore(@virt, potential_ram_only_snapshot_path)
610
611
612
613
614
615
616
617
618
      @domain = @virt.lookup_domain_by_name(@domain_name)
    else
      begin
        potential_internal_snapshot = @domain.lookup_snapshot_by_name(name)
        @domain.revert_to_snapshot(potential_internal_snapshot)
      rescue Libvirt::RetrieveError
        raise "No such (internal nor external) snapshot #{name}"
      end
    end
619
620
621
    @display.start
  end

622
  def VM.remove_snapshot(name)
623
    old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME)
624
    potential_ram_only_snapshot_path = VM.ram_only_snapshot_path(name)
625
626
627
    if File.exist?(potential_ram_only_snapshot_path)
      File.delete(potential_ram_only_snapshot_path)
    else
628
      snapshot = old_domain.lookup_snapshot_by_name(name)
629
630
      snapshot.delete
    end
631
632
633
  end

  def VM.snapshot_exists?(name)
634
    return true if File.exist?(VM.ram_only_snapshot_path(name))
635
    old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME)
636
637
638
639
640
641
642
    snapshot = old_domain.lookup_snapshot_by_name(name)
    return snapshot != nil
  rescue Libvirt::RetrieveError
    return false
  end

  def VM.remove_all_snapshots
643
    Dir.glob("#{$config["TMPDIR"]}/*-snapshot.memstate").each do |file|
644
645
      File.delete(file)
    end
646
    old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME)
647
648
649
650
651
    old_domain.list_all_snapshots.each { |snapshot| snapshot.delete }
  rescue Libvirt::RetrieveError
    # No such domain, so no snapshots either.
  end

652
  def start
653
    return if is_running?
654
    @domain.create
655
    @display.start
656
657
  end

658
  def reset
659
    @domain.reset if is_running?
660
661
  end

662
  def power_off
663
    @domain.destroy if is_running?
664
    @display.stop
665
  end
666
667
668
669

  def take_screenshot(description)
    @display.take_screenshot(description)
  end
670

671
  def get_remote_shell_port
672
673
    domain_xml = REXML::Document.new(@domain.xml_desc)
    domain_xml.elements.each('domain/devices/serial') do |e|
674
675
      if e.attribute('type').to_s == "tcp"
        return e.elements['source'].attribute('service').to_s.to_i
676
677
678
679
      end
    end
  end

680
end