vagrant-libvirt 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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/>