vagrant-libvirt 0.4.0 → 0.5.3

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +251 -33
  3. data/lib/vagrant-libvirt/action.rb +7 -1
  4. data/lib/vagrant-libvirt/action/clean_machine_folder.rb +30 -0
  5. data/lib/vagrant-libvirt/action/create_domain.rb +28 -11
  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 +170 -77
  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/set_boot_order.rb +6 -2
  16. data/lib/vagrant-libvirt/action/wait_till_up.rb +1 -25
  17. data/lib/vagrant-libvirt/cap/{mount_p9.rb → mount_9p.rb} +2 -2
  18. data/lib/vagrant-libvirt/cap/mount_virtiofs.rb +37 -0
  19. data/lib/vagrant-libvirt/cap/{synced_folder.rb → synced_folder_9p.rb} +4 -5
  20. data/lib/vagrant-libvirt/cap/synced_folder_virtiofs.rb +109 -0
  21. data/lib/vagrant-libvirt/config.rb +34 -2
  22. data/lib/vagrant-libvirt/driver.rb +3 -1
  23. data/lib/vagrant-libvirt/errors.rb +24 -1
  24. data/lib/vagrant-libvirt/plugin.rb +14 -5
  25. data/lib/vagrant-libvirt/templates/domain.xml.erb +7 -6
  26. data/lib/vagrant-libvirt/templates/private_network.xml.erb +1 -1
  27. data/lib/vagrant-libvirt/util/byte_number.rb +71 -0
  28. data/lib/vagrant-libvirt/util/network_util.rb +21 -3
  29. data/lib/vagrant-libvirt/version +1 -1
  30. data/locales/en.yml +12 -0
  31. data/spec/spec_helper.rb +9 -1
  32. data/spec/support/binding_proc.rb +24 -0
  33. data/spec/support/matchers/have_file_content.rb +63 -0
  34. data/spec/unit/action/clean_machine_folder_spec.rb +58 -0
  35. data/spec/unit/action/create_domain_spec.rb +15 -5
  36. data/spec/unit/action/create_domain_spec/additional_disks_domain.xml +54 -0
  37. data/spec/unit/action/create_domain_spec/default_domain.xml +49 -0
  38. data/spec/unit/action/create_domain_volume_spec.rb +104 -0
  39. data/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml +21 -0
  40. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml +21 -0
  41. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml +21 -0
  42. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml +21 -0
  43. data/spec/unit/action/destroy_domain_spec.rb +1 -1
  44. data/spec/unit/action/forward_ports_spec.rb +202 -0
  45. data/spec/unit/action/halt_domain_spec.rb +90 -0
  46. data/spec/unit/action/handle_box_image_spec.rb +441 -0
  47. data/spec/unit/action/wait_till_up_spec.rb +11 -15
  48. data/spec/unit/config_spec.rb +12 -9
  49. data/spec/unit/templates/domain_all_settings.xml +8 -0
  50. data/spec/unit/templates/domain_spec.rb +20 -1
  51. data/spec/unit/util/byte_number_spec.rb +26 -0
  52. metadata +52 -18
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'cgi'
2
+
1
3
  require 'vagrant'
2
4
 
3
5
  class Numeric
@@ -39,6 +41,9 @@ module VagrantPlugins
39
41
 
40
42
  attr_accessor :proxy_command
41
43
 
44
+ # Forward port with id 'ssh'
45
+ attr_accessor :forward_ssh_port
46
+
42
47
  # Libvirt storage pool name, where box image and instance snapshots will
43
48
  # be stored.
44
49
  attr_accessor :storage_pool_name
@@ -61,6 +66,7 @@ module VagrantPlugins
61
66
  attr_accessor :management_network_pci_bus
62
67
  attr_accessor :management_network_pci_slot
63
68
  attr_accessor :management_network_domain
69
+ attr_accessor :management_network_mtu
64
70
 
65
71
  # System connection information
66
72
  attr_accessor :system_uri
@@ -189,12 +195,14 @@ module VagrantPlugins
189
195
  @uri = UNSET_VALUE
190
196
  @driver = UNSET_VALUE
191
197
  @host = UNSET_VALUE
198
+ @port = UNSET_VALUE
192
199
  @connect_via_ssh = UNSET_VALUE
193
200
  @username = UNSET_VALUE
194
201
  @password = UNSET_VALUE
195
202
  @id_ssh_key_file = UNSET_VALUE
196
203
  @socket = UNSET_VALUE
197
204
  @proxy_command = UNSET_VALUE
205
+ @forward_ssh_port = UNSET_VALUE # forward port with id 'ssh'
198
206
  @storage_pool_name = UNSET_VALUE
199
207
  @snapshot_pool_name = UNSET_VALUE
200
208
  @random_hostname = UNSET_VALUE
@@ -208,6 +216,7 @@ module VagrantPlugins
208
216
  @management_network_pci_slot = UNSET_VALUE
209
217
  @management_network_pci_bus = UNSET_VALUE
210
218
  @management_network_domain = UNSET_VALUE
219
+ @management_network_mtu = UNSET_VALUE
211
220
 
212
221
  # System connection information
213
222
  @system_uri = UNSET_VALUE
@@ -396,10 +405,20 @@ module VagrantPlugins
396
405
  raise 'Feature name AND state must be specified'
397
406
  end
398
407
 
408
+ if options[:name] == 'spinlocks' && options[:retries].nil?
409
+ raise 'Feature spinlocks requires retries parameter'
410
+ end
411
+
399
412
  @features_hyperv = [] if @features_hyperv == UNSET_VALUE
400
413
 
401
- @features_hyperv.push(name: options[:name],
402
- state: options[:state])
414
+ if options[:name] == 'spinlocks'
415
+ @features_hyperv.push(name: options[:name],
416
+ state: options[:state],
417
+ retries: options[:retries])
418
+ else
419
+ @features_hyperv.push(name: options[:name],
420
+ state: options[:state])
421
+ end
403
422
  end
404
423
 
405
424
  def clock_timer(options = {})
@@ -622,11 +641,13 @@ module VagrantPlugins
622
641
  # as will the address unit number (unit=0, unit=1, etc)
623
642
 
624
643
  options = {
644
+ type: 'raw',
625
645
  bus: 'ide',
626
646
  path: nil
627
647
  }.merge(options)
628
648
 
629
649
  cdrom = {
650
+ type: options[:type],
630
651
  dev: options[:dev],
631
652
  bus: options[:bus],
632
653
  path: options[:path]
@@ -770,6 +791,9 @@ module VagrantPlugins
770
791
  finalize_from_uri
771
792
  finalize_proxy_command
772
793
 
794
+ # forward port with id 'ssh'
795
+ @forward_ssh_port = false if @forward_ssh_port == UNSET_VALUE
796
+
773
797
  @storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
774
798
  @snapshot_pool_name = @storage_pool_name if @snapshot_pool_name == UNSET_VALUE
775
799
  @storage_pool_path = nil if @storage_pool_path == UNSET_VALUE
@@ -784,6 +808,7 @@ module VagrantPlugins
784
808
  @management_network_pci_bus = nil if @management_network_pci_bus == UNSET_VALUE
785
809
  @management_network_pci_slot = nil if @management_network_pci_slot == UNSET_VALUE
786
810
  @management_network_domain = nil if @management_network_domain == UNSET_VALUE
811
+ @management_network_mtu = nil if @management_network_mtu == UNSET_VALUE
787
812
  @system_uri = 'qemu:///system' if @system_uri == UNSET_VALUE
788
813
 
789
814
  # Domain specific settings.
@@ -994,7 +1019,12 @@ module VagrantPlugins
994
1019
 
995
1020
  # Extract host and username values from uri if provided, otherwise nil
996
1021
  @host = uri.host
1022
+ @port = uri.port
997
1023
  @username = uri.user
1024
+ if uri.query
1025
+ params = CGI.parse(uri.query)
1026
+ @id_ssh_key_file = params['keyfile'].first if params.has_key?('keyfile')
1027
+ end
998
1028
 
999
1029
  finalize_id_ssh_key_file
1000
1030
  end
@@ -1031,11 +1061,13 @@ module VagrantPlugins
1031
1061
  if @connect_via_ssh
1032
1062
  if @proxy_command == UNSET_VALUE
1033
1063
  proxy_command = "ssh '#{@host}' "
1064
+ proxy_command << "-p #{@port} " if @port
1034
1065
  proxy_command << "-l '#{@username}' " if @username
1035
1066
  proxy_command << "-i '#{@id_ssh_key_file}' " if @id_ssh_key_file
1036
1067
  proxy_command << '-W %h:%p'
1037
1068
  else
1038
1069
  inputs = { host: @host }
1070
+ inputs << { port: @port } if @port
1039
1071
  inputs[:username] = @username if @username
1040
1072
  inputs[:id_ssh_key_file] = @id_ssh_key_file if @id_ssh_key_file
1041
1073
 
@@ -97,11 +97,13 @@ module VagrantPlugins
97
97
  return get_ipaddress_from_system domain.mac
98
98
  end
99
99
 
100
- # Get IP address from arp table
100
+ # Get IP address from dhcp leases table
101
101
  begin
102
102
  ip_address = get_ipaddress_from_domain(domain)
103
103
  rescue Fog::Errors::TimeoutError
104
104
  @logger.info('Timeout at waiting for an ip address for machine %s' % machine.name)
105
+
106
+ raise
105
107
  end
106
108
 
107
109
  unless ip_address
@@ -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)
@@ -26,12 +26,17 @@ module VagrantPlugins
26
26
  end
27
27
 
28
28
  action_hook(:remove_libvirt_image) do |hook|
29
+ require_relative 'action'
29
30
  hook.after Vagrant::Action::Builtin::BoxRemove, Action.remove_libvirt_image
30
31
  end
31
32
 
32
- guest_capability('linux', 'mount_p9_shared_folder') do
33
- require_relative 'cap/mount_p9'
34
- 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
35
40
  end
36
41
 
37
42
  provider_capability(:libvirt, :nic_mac_addresses) do
@@ -47,8 +52,12 @@ module VagrantPlugins
47
52
  # lower priority than nfs or rsync
48
53
  # https://github.com/vagrant-libvirt/vagrant-libvirt/pull/170
49
54
  synced_folder('9p', 4) do
50
- require_relative 'cap/synced_folder'
51
- 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
52
61
  end
53
62
 
54
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/>
@@ -1,6 +1,6 @@
1
1
  <network ipv6='<%= @guest_ipv6 %>'>
2
2
  <name><%= @network_name %></name>
3
- <bridge name="<%= @network_bridge_name %>" />
3
+ <bridge name="<%= @network_bridge_name %>" stp="<%= @network_bridge_stp %>" delay="<%= @network_bridge_delay %>" />
4
4
 
5
5
  <% if @network_domain_name %>
6
6
  <domain name="<%= @network_domain_name %>" localOnly="yes" />