vagrant-libvirt 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +212 -27
  3. data/lib/vagrant-libvirt/action.rb +6 -0
  4. data/lib/vagrant-libvirt/action/clean_machine_folder.rb +28 -0
  5. data/lib/vagrant-libvirt/action/create_domain.rb +26 -10
  6. data/lib/vagrant-libvirt/action/create_domain_volume.rb +57 -55
  7. data/lib/vagrant-libvirt/action/create_network_interfaces.rb +0 -3
  8. data/lib/vagrant-libvirt/action/create_networks.rb +11 -4
  9. data/lib/vagrant-libvirt/action/destroy_domain.rb +1 -1
  10. data/lib/vagrant-libvirt/action/forward_ports.rb +36 -37
  11. data/lib/vagrant-libvirt/action/halt_domain.rb +25 -9
  12. data/lib/vagrant-libvirt/action/handle_box_image.rb +162 -74
  13. data/lib/vagrant-libvirt/action/is_running.rb +1 -3
  14. data/lib/vagrant-libvirt/action/is_suspended.rb +4 -4
  15. data/lib/vagrant-libvirt/action/wait_till_up.rb +1 -25
  16. data/lib/vagrant-libvirt/cap/{mount_p9.rb → mount_9p.rb} +2 -2
  17. data/lib/vagrant-libvirt/cap/mount_virtiofs.rb +37 -0
  18. data/lib/vagrant-libvirt/cap/{synced_folder.rb → synced_folder_9p.rb} +4 -5
  19. data/lib/vagrant-libvirt/cap/synced_folder_virtiofs.rb +109 -0
  20. data/lib/vagrant-libvirt/config.rb +24 -2
  21. data/lib/vagrant-libvirt/errors.rb +24 -1
  22. data/lib/vagrant-libvirt/plugin.rb +13 -5
  23. data/lib/vagrant-libvirt/templates/domain.xml.erb +7 -6
  24. data/lib/vagrant-libvirt/templates/private_network.xml.erb +1 -1
  25. data/lib/vagrant-libvirt/util/network_util.rb +21 -3
  26. data/lib/vagrant-libvirt/version +1 -1
  27. data/locales/en.yml +12 -0
  28. data/spec/spec_helper.rb +9 -1
  29. data/spec/support/matchers/have_file_content.rb +63 -0
  30. data/spec/unit/action/clean_machine_folder_spec.rb +48 -0
  31. data/spec/unit/action/create_domain_spec.rb +6 -0
  32. data/spec/unit/action/create_domain_volume_spec.rb +102 -0
  33. data/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml +21 -0
  34. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml +21 -0
  35. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml +21 -0
  36. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml +21 -0
  37. data/spec/unit/action/destroy_domain_spec.rb +1 -1
  38. data/spec/unit/action/forward_ports_spec.rb +202 -0
  39. data/spec/unit/action/halt_domain_spec.rb +90 -0
  40. data/spec/unit/action/handle_box_image_spec.rb +363 -0
  41. data/spec/unit/action/wait_till_up_spec.rb +1 -23
  42. data/spec/unit/templates/domain_all_settings.xml +8 -0
  43. data/spec/unit/templates/domain_spec.rb +20 -1
  44. metadata +41 -4
@@ -9,9 +9,7 @@ module VagrantPlugins
9
9
  end
10
10
 
11
11
  def call(env)
12
- domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
13
- raise Errors::NoDomainError if domain.nil?
14
- env[:result] = domain.state.to_s == 'running'
12
+ env[:result] = env[:machine].state.id == :running
15
13
 
16
14
  @app.call(env)
17
15
  end
@@ -16,9 +16,9 @@ module VagrantPlugins
16
16
  libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(env[:machine].id)
17
17
  if config.suspend_mode == 'managedsave'
18
18
  if libvirt_domain.has_managed_save?
19
- env[:result] = libvirt_domain.has_managed_save?
19
+ env[:result] = env[:machine].state.id == :shutoff
20
20
  else
21
- env[:result] = domain.state.to_s == 'paused'
21
+ env[:result] = env[:machine].state.id == :paused
22
22
  if env[:result]
23
23
  env[:ui].warn('One time switching to pause suspend mode, found a paused VM.')
24
24
  config.suspend_mode = 'pause'
@@ -27,10 +27,10 @@ module VagrantPlugins
27
27
  else
28
28
  if libvirt_domain.has_managed_save?
29
29
  env[:ui].warn('One time switching to managedsave suspend mode, state found.')
30
- env[:result] = true
30
+ env[:result] = [:shutoff, :paused].include?(env[:machine].state.id)
31
31
  config.suspend_mode = 'managedsave'
32
32
  else
33
- env[:result] = domain.state.to_s == 'paused'
33
+ env[:result] = env[:machine].state.id == :paused
34
34
  end
35
35
  end
36
36
 
@@ -47,30 +47,6 @@ module VagrantPlugins
47
47
  @logger.info("Got IP address #{env[:ip_address]}")
48
48
  @logger.info("Time for getting IP: #{env[:metrics]['instance_ip_time']}")
49
49
 
50
- # Machine has ip address assigned, now wait till we are able to
51
- # connect via ssh.
52
- env[:metrics]['instance_ssh_time'] = Util::Timer.time do
53
- env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ssh'))
54
- retryable(on: Fog::Errors::TimeoutError, tries: 60) do
55
- # If we're interrupted don't worry about waiting
56
- next if env[:interrupted]
57
-
58
- # Wait till we are able to connect via ssh.
59
- loop do
60
- # If we're interrupted then just back out
61
- break if env[:interrupted]
62
- break if env[:machine].communicate.ready?
63
- sleep 2
64
- end
65
- end
66
- end
67
- # just return if interrupted and let the warden call recover
68
- return if env[:interrupted]
69
- @logger.info("Time for SSH ready: #{env[:metrics]['instance_ssh_time']}")
70
-
71
- # Booted and ready for use.
72
- # env[:ui].info(I18n.t("vagrant_libvirt.ready"))
73
-
74
50
  @app.call(env)
75
51
  end
76
52
 
@@ -80,7 +56,7 @@ module VagrantPlugins
80
56
  end
81
57
 
82
58
  def terminate(env)
83
- if env[:machine].provider.state.id != :not_created
59
+ if env[:machine].state.id != :not_created
84
60
  # If we're not supposed to destroy on error then just return
85
61
  return unless env[:destroy_on_error]
86
62
 
@@ -4,10 +4,10 @@ require 'vagrant/util/retryable'
4
4
  module VagrantPlugins
5
5
  module ProviderLibvirt
6
6
  module Cap
7
- class MountP9
7
+ class Mount9P
8
8
  extend Vagrant::Util::Retryable
9
9
 
10
- def self.mount_p9_shared_folder(machine, folders)
10
+ def self.mount_9p_shared_folder(machine, folders)
11
11
  folders.each do |_name, opts|
12
12
  # Expand the guest path so we can handle things like "~/vagrant"
13
13
  expanded_guest_path = machine.guest.capability(
@@ -0,0 +1,37 @@
1
+ require 'digest/md5'
2
+ require 'vagrant/util/retryable'
3
+
4
+ module VagrantPlugins
5
+ module ProviderLibvirt
6
+ module Cap
7
+ class MountVirtioFS
8
+ extend Vagrant::Util::Retryable
9
+
10
+ def self.mount_virtiofs_shared_folder(machine, folders)
11
+ folders.each do |_name, opts|
12
+ # Expand the guest path so we can handle things like "~/vagrant"
13
+ expanded_guest_path = machine.guest.capability(
14
+ :shell_expand_guest_path, opts[:guestpath]
15
+ )
16
+
17
+ # Do the actual creating and mounting
18
+ machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
19
+
20
+ # Mount
21
+ mount_tag = Digest::MD5.new.update(opts[:hostpath]).to_s[0, 31]
22
+
23
+ mount_opts = "-o #{opts[:mount_opts]}" if opts[:mount_opts]
24
+
25
+ mount_command = "mount -t virtiofs #{mount_opts} '#{mount_tag}' #{expanded_guest_path}"
26
+ retryable(on: Vagrant::Errors::LinuxMountFailed,
27
+ tries: 5,
28
+ sleep: 3) do
29
+ machine.communicate.sudo(mount_command,
30
+ error_class: Vagrant::Errors::LinuxMountFailed)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -6,10 +6,9 @@ require 'digest/md5'
6
6
  require 'vagrant/util/subprocess'
7
7
  require 'vagrant/errors'
8
8
  require 'vagrant-libvirt/errors'
9
- # require_relative "helper"
10
9
 
11
10
  module VagrantPlugins
12
- module SyncedFolder9p
11
+ module SyncedFolder9P
13
12
  class SyncedFolder < Vagrant.plugin('2', :synced_folder)
14
13
  include Vagrant::Util
15
14
  include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
@@ -69,10 +68,10 @@ module VagrantPlugins
69
68
  end
70
69
  end
71
70
 
72
- # TODO: once up, mount folders
71
+ # once up, mount folders
73
72
  def enable(machine, folders, _opts)
74
73
  # Go through each folder and mount
75
- machine.ui.info('mounting p9 share in guest')
74
+ machine.ui.info('mounting 9p share in guest')
76
75
  # Only mount folders that have a guest path specified.
77
76
  mount_folders = {}
78
77
  folders.each do |id, opts|
@@ -83,7 +82,7 @@ module VagrantPlugins
83
82
  end
84
83
  # Mount the actual folder
85
84
  machine.guest.capability(
86
- :mount_p9_shared_folder, mount_folders
85
+ :mount_9p_shared_folder, mount_folders
87
86
  )
88
87
  end
89
88
 
@@ -0,0 +1,109 @@
1
+ require 'log4r'
2
+ require 'ostruct'
3
+ require 'nokogiri'
4
+ require 'digest/md5'
5
+
6
+ require 'vagrant/util/subprocess'
7
+ require 'vagrant/errors'
8
+ require 'vagrant-libvirt/errors'
9
+
10
+ module VagrantPlugins
11
+ module SyncedFolderVirtioFS
12
+ class SyncedFolder < Vagrant.plugin('2', :synced_folder)
13
+ include Vagrant::Util
14
+ include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
15
+
16
+ def initialize(*args)
17
+ super
18
+ @logger = Log4r::Logger.new('vagrant_libvirt::synced_folders::virtiofs')
19
+ end
20
+
21
+ def usable?(machine, _raise_error = false)
22
+ # bail now if not using Libvirt since checking version would throw error
23
+ return false unless machine.provider_name == :libvirt
24
+
25
+ # virtiofs support introduced since 6.2.0
26
+ # version number format is major * 1,000,000 + minor * 1,000 + release
27
+ libvirt_version = machine.provider.driver.connection.client.libversion
28
+ libvirt_version >= 6_002_000
29
+ end
30
+
31
+ def prepare(machine, folders, _opts)
32
+ raise Vagrant::Errors::Error('No Libvirt connection') if machine.provider.driver.connection.nil?
33
+ @conn = machine.provider.driver.connection.client
34
+
35
+ begin
36
+ # loop through folders
37
+ folders.each do |id, folder_opts|
38
+ folder_opts.merge!(target: id,
39
+ mount: true,
40
+ readonly: nil) { |_k, ov, _nv| ov }
41
+
42
+ mount_tag = Digest::MD5.new.update(folder_opts[:hostpath]).to_s[0, 31]
43
+ folder_opts[:mount_tag] = mount_tag
44
+
45
+ machine.ui.info "================\nMachine id: #{machine.id}\nShould be mounting folders\n #{id}, opts: #{folder_opts}"
46
+
47
+ #xml = to_xml('filesystem', folder_opts)
48
+ xml = Nokogiri::XML::Builder.new do |xml|
49
+ xml.filesystem(type: 'mount', accessmode: 'passthrough') do
50
+ xml.driver(type: 'virtiofs')
51
+ xml.source(dir: folder_opts[:hostpath])
52
+ xml.target(dir: mount_tag)
53
+ xml.readonly unless folder_opts[:readonly].nil?
54
+ end
55
+ end.to_xml(
56
+ save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
57
+ Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
58
+ Nokogiri::XML::Node::SaveOptions::FORMAT
59
+ )
60
+ # puts "<<<<< XML:\n #{xml}\n >>>>>"
61
+ @conn.lookup_domain_by_uuid(machine.id).attach_device(xml, 0)
62
+ end
63
+ rescue => e
64
+ machine.ui.error("could not attach device because: #{e}")
65
+ raise VagrantPlugins::ProviderLibvirt::Errors::AttachDeviceError,
66
+ error_message: e.message
67
+ end
68
+ end
69
+
70
+ # once up, mount folders
71
+ def enable(machine, folders, _opts)
72
+ # Go through each folder and mount
73
+ machine.ui.info('mounting virtiofs share in guest')
74
+ # Only mount folders that have a guest path specified.
75
+ mount_folders = {}
76
+ folders.each do |id, opts|
77
+ next unless opts[:mount] && opts[:guestpath] && !opts[:guestpath].empty?
78
+ mount_folders[id] = opts.dup
79
+ end
80
+ # Mount the actual folder
81
+ machine.guest.capability(
82
+ :mount_virtiofs_shared_folder, mount_folders
83
+ )
84
+ end
85
+
86
+ def cleanup(machine, _opts)
87
+ if machine.provider.driver.connection.nil?
88
+ raise Vagrant::Errors::Error('No Libvirt connection')
89
+ end
90
+ @conn = machine.provider.driver.connection.client
91
+ begin
92
+ if machine.id && machine.id != ''
93
+ dom = @conn.lookup_domain_by_uuid(machine.id)
94
+ Nokogiri::XML(dom.xml_desc).xpath(
95
+ '/domain/devices/filesystem'
96
+ ).each do |xml|
97
+ dom.detach_device(xml.to_s)
98
+ machine.ui.info 'Cleaned up shared folders'
99
+ end
100
+ end
101
+ rescue => e
102
+ machine.ui.error("could not detach device because: #{e}")
103
+ raise VagrantPlugins::ProviderLibvirt::Errors::DetachDeviceError,
104
+ error_message: e.message
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -39,6 +39,9 @@ module VagrantPlugins
39
39
 
40
40
  attr_accessor :proxy_command
41
41
 
42
+ # Forward port with id 'ssh'
43
+ attr_accessor :forward_ssh_port
44
+
42
45
  # Libvirt storage pool name, where box image and instance snapshots will
43
46
  # be stored.
44
47
  attr_accessor :storage_pool_name
@@ -61,6 +64,7 @@ module VagrantPlugins
61
64
  attr_accessor :management_network_pci_bus
62
65
  attr_accessor :management_network_pci_slot
63
66
  attr_accessor :management_network_domain
67
+ attr_accessor :management_network_mtu
64
68
 
65
69
  # System connection information
66
70
  attr_accessor :system_uri
@@ -195,6 +199,7 @@ module VagrantPlugins
195
199
  @id_ssh_key_file = UNSET_VALUE
196
200
  @socket = UNSET_VALUE
197
201
  @proxy_command = UNSET_VALUE
202
+ @forward_ssh_port = UNSET_VALUE # forward port with id 'ssh'
198
203
  @storage_pool_name = UNSET_VALUE
199
204
  @snapshot_pool_name = UNSET_VALUE
200
205
  @random_hostname = UNSET_VALUE
@@ -208,6 +213,7 @@ module VagrantPlugins
208
213
  @management_network_pci_slot = UNSET_VALUE
209
214
  @management_network_pci_bus = UNSET_VALUE
210
215
  @management_network_domain = UNSET_VALUE
216
+ @management_network_mtu = UNSET_VALUE
211
217
 
212
218
  # System connection information
213
219
  @system_uri = UNSET_VALUE
@@ -396,10 +402,20 @@ module VagrantPlugins
396
402
  raise 'Feature name AND state must be specified'
397
403
  end
398
404
 
405
+ if options[:name] == 'spinlocks' && options[:retries].nil?
406
+ raise 'Feature spinlocks requires retries parameter'
407
+ end
408
+
399
409
  @features_hyperv = [] if @features_hyperv == UNSET_VALUE
400
410
 
401
- @features_hyperv.push(name: options[:name],
402
- state: options[:state])
411
+ if options[:name] == 'spinlocks'
412
+ @features_hyperv.push(name: options[:name],
413
+ state: options[:state],
414
+ retries: options[:retries])
415
+ else
416
+ @features_hyperv.push(name: options[:name],
417
+ state: options[:state])
418
+ end
403
419
  end
404
420
 
405
421
  def clock_timer(options = {})
@@ -622,11 +638,13 @@ module VagrantPlugins
622
638
  # as will the address unit number (unit=0, unit=1, etc)
623
639
 
624
640
  options = {
641
+ type: 'raw',
625
642
  bus: 'ide',
626
643
  path: nil
627
644
  }.merge(options)
628
645
 
629
646
  cdrom = {
647
+ type: options[:type],
630
648
  dev: options[:dev],
631
649
  bus: options[:bus],
632
650
  path: options[:path]
@@ -770,6 +788,9 @@ module VagrantPlugins
770
788
  finalize_from_uri
771
789
  finalize_proxy_command
772
790
 
791
+ # forward port with id 'ssh'
792
+ @forward_ssh_port = false if @forward_ssh_port == UNSET_VALUE
793
+
773
794
  @storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
774
795
  @snapshot_pool_name = @storage_pool_name if @snapshot_pool_name == UNSET_VALUE
775
796
  @storage_pool_path = nil if @storage_pool_path == UNSET_VALUE
@@ -784,6 +805,7 @@ module VagrantPlugins
784
805
  @management_network_pci_bus = nil if @management_network_pci_bus == UNSET_VALUE
785
806
  @management_network_pci_slot = nil if @management_network_pci_slot == UNSET_VALUE
786
807
  @management_network_domain = nil if @management_network_domain == UNSET_VALUE
808
+ @management_network_mtu = nil if @management_network_mtu == UNSET_VALUE
787
809
  @system_uri = 'qemu:///system' if @system_uri == UNSET_VALUE
788
810
 
789
811
  # Domain specific settings.
@@ -37,7 +37,22 @@ module VagrantPlugins
37
37
  error_key(:image_download_error)
38
38
  end
39
39
 
40
- # Box exceptions
40
+ # Box exceptions, capture all under one
41
+ class BoxError < VagrantLibvirtError
42
+ end
43
+
44
+ class BoxFormatMissingAttribute < BoxError
45
+ error_key(:box_format_missing_attribute)
46
+ end
47
+
48
+ class BoxFormatDuplicateVolume < BoxError
49
+ error_key(:box_format_duplicate_volume)
50
+ end
51
+
52
+ class BadBoxImage < VagrantLibvirtError
53
+ error_key(:bad_box_image)
54
+ end
55
+
41
56
  class NoBoxVolume < VagrantLibvirtError
42
57
  error_key(:no_box_volume)
43
58
  end
@@ -46,6 +61,10 @@ module VagrantPlugins
46
61
  error_key(:no_box_virtual_size)
47
62
  end
48
63
 
64
+ class NoDiskVirtualSizeSet < VagrantLibvirtError
65
+ error_key(:no_disk_virtual_size)
66
+ end
67
+
49
68
  class NoBoxFormatSet < VagrantLibvirtError
50
69
  error_key(:no_box_format)
51
70
  end
@@ -54,6 +73,10 @@ module VagrantPlugins
54
73
  error_key(:wrong_box_format)
55
74
  end
56
75
 
76
+ class WrongDiskFormatSet < VagrantLibvirtError
77
+ error_key(:wrong_disk_format)
78
+ end
79
+
57
80
  # Fog Libvirt exceptions
58
81
  class FogError < VagrantLibvirtError
59
82
  error_key(:fog_error)
@@ -30,9 +30,13 @@ module VagrantPlugins
30
30
  hook.after Vagrant::Action::Builtin::BoxRemove, Action.remove_libvirt_image
31
31
  end
32
32
 
33
- guest_capability('linux', 'mount_p9_shared_folder') do
34
- require_relative 'cap/mount_p9'
35
- Cap::MountP9
33
+ guest_capability('linux', 'mount_9p_shared_folder') do
34
+ require_relative 'cap/mount_9p'
35
+ Cap::Mount9P
36
+ end
37
+ guest_capability('linux', 'mount_virtiofs_shared_folder') do
38
+ require_relative 'cap/mount_virtiofs'
39
+ Cap::MountVirtioFS
36
40
  end
37
41
 
38
42
  provider_capability(:libvirt, :nic_mac_addresses) do
@@ -48,8 +52,12 @@ module VagrantPlugins
48
52
  # lower priority than nfs or rsync
49
53
  # https://github.com/vagrant-libvirt/vagrant-libvirt/pull/170
50
54
  synced_folder('9p', 4) do
51
- require_relative 'cap/synced_folder'
52
- VagrantPlugins::SyncedFolder9p::SyncedFolder
55
+ require_relative 'cap/synced_folder_9p'
56
+ VagrantPlugins::SyncedFolder9P::SyncedFolder
57
+ end
58
+ synced_folder('virtiofs', 5) do
59
+ require_relative 'cap/synced_folder_virtiofs'
60
+ VagrantPlugins::SyncedFolderVirtioFS::SyncedFolder
53
61
  end
54
62
 
55
63
  # This initializes the internationalization strings.
@@ -99,7 +99,7 @@
99
99
  <% if !@features_hyperv.empty? %>
100
100
  <hyperv>
101
101
  <% @features_hyperv.each do |feature| %>
102
- <<%= feature[:name] %> state='<%= feature[:state] %>' />
102
+ <<%= feature[:name] %> state='<%= feature[:state] %>'<% if feature[:name] == 'spinlocks' %> retries='<%= feature[:retries] %>'<% end %> />
103
103
  <% end %>
104
104
  </hyperv>
105
105
  <% end %>
@@ -113,18 +113,18 @@
113
113
  <% if @emulator_path %>
114
114
  <emulator><%= @emulator_path %></emulator>
115
115
  <% end %>
116
- <% if @domain_volume_path %>
116
+ <% @domain_volumes.each do |volume| -%>
117
117
  <disk type='file' device='disk'>
118
118
  <driver name='qemu' type='qcow2' <%=
119
- @disk_driver_opts.empty? ? "cache='#{@domain_volume_cache}'" :
119
+ @disk_driver_opts.empty? ? "cache='#{volume[:cache]}'" :
120
120
  @disk_driver_opts.reject { |k,v| v.nil? }
121
121
  .map { |k,v| "#{k}='#{v}'"}
122
122
  .join(' ') -%>/>
123
- <source file='<%= @domain_volume_path %>'/>
123
+ <source file='<%= volume[:path] %>'/>
124
124
  <%# we need to ensure a unique target dev -%>
125
- <target dev='<%= @disk_device %>' bus='<%= @disk_bus %>'/>
125
+ <target dev='<%= volume[:dev] %>' bus='<%= volume[:bus] %>'/>
126
126
  </disk>
127
- <% end %>
127
+ <% end -%>
128
128
  <%# additional disks -%>
129
129
  <% @disks.each do |d| -%>
130
130
  <disk type='file' device='disk'>
@@ -153,6 +153,7 @@
153
153
 
154
154
  <% @cdroms.each do |c| %>
155
155
  <disk type='file' device='cdrom'>
156
+ <driver name='qemu' type='<%= c[:type] %>' />
156
157
  <source file='<%= c[:path] %>'/>
157
158
  <target dev='<%= c[:dev] %>' bus='<%= c[:bus] %>'/>
158
159
  <readonly/>