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
@@ -18,72 +18,74 @@ module VagrantPlugins
18
18
  def call(env)
19
19
  env[:ui].info(I18n.t('vagrant_libvirt.creating_domain_volume'))
20
20
 
21
- # Get config options.
22
- config = env[:machine].provider_config
21
+ env[:box_volumes].each_index do |index|
22
+ suffix_index = index > 0 ? "_#{index}" : ''
23
+ # Get config options.
24
+ config = env[:machine].provider_config
23
25
 
24
- # This is name of newly created image for vm.
25
- @name = "#{env[:domain_name]}.img"
26
+ # This is name of newly created image for vm.
27
+ @name = "#{env[:domain_name]}#{suffix_index}.img"
26
28
 
27
- # Verify the volume doesn't exist already.
28
- domain_volume = env[:machine].provider.driver.connection.volumes.all(
29
- name: @name
30
- ).first
31
- raise Errors::DomainVolumeExists if domain_volume && domain_volume.id
29
+ # Verify the volume doesn't exist already.
30
+ domain_volume = env[:machine].provider.driver.connection.volumes.all(
31
+ name: @name
32
+ ).first
33
+ raise Errors::DomainVolumeExists if domain_volume && domain_volume.id
32
34
 
33
- # Get path to backing image - box volume.
34
- box_volume = env[:machine].provider.driver.connection.volumes.all(
35
- name: env[:box_volume_name]
36
- ).first
37
- @backing_file = box_volume.path
35
+ # Get path to backing image - box volume.
36
+ box_volume = env[:machine].provider.driver.connection.volumes.all(
37
+ name: env[:box_volumes][index][:name]
38
+ ).first
39
+ @backing_file = box_volume.path
38
40
 
39
- # Virtual size of image. Take value worked out by HandleBoxImage
40
- @capacity = env[:box_virtual_size] # G
41
+ # Virtual size of image. Take value worked out by HandleBoxImage
42
+ @capacity = env[:box_volumes][index][:virtual_size] # G
41
43
 
42
- # Create new volume from xml template. Fog currently doesn't support
43
- # volume snapshots directly.
44
- begin
45
- xml = Nokogiri::XML::Builder.new do |xml|
46
- xml.volume do
47
- xml.name(@name)
48
- xml.capacity(@capacity, unit: 'G')
49
- xml.target do
50
- xml.format(type: 'qcow2')
51
- xml.permissions do
52
- xml.owner storage_uid(env)
53
- xml.group storage_gid(env)
54
- xml.label 'virt_image_t'
44
+ # Create new volume from xml template. Fog currently doesn't support
45
+ # volume snapshots directly.
46
+ begin
47
+ xml = Nokogiri::XML::Builder.new do |xml|
48
+ xml.volume do
49
+ xml.name(@name)
50
+ xml.capacity(@capacity, unit: 'G')
51
+ xml.target do
52
+ xml.format(type: 'qcow2')
53
+ xml.permissions do
54
+ xml.owner storage_uid(env)
55
+ xml.group storage_gid(env)
56
+ xml.label 'virt_image_t'
57
+ end
55
58
  end
56
- end
57
- xml.backingStore do
58
- xml.path(@backing_file)
59
- xml.format(type: 'qcow2')
60
- xml.permissions do
61
- xml.owner storage_uid(env)
62
- xml.group storage_gid(env)
63
- xml.label 'virt_image_t'
59
+ xml.backingStore do
60
+ xml.path(@backing_file)
61
+ xml.format(type: 'qcow2')
62
+ xml.permissions do
63
+ xml.owner storage_uid(env)
64
+ xml.group storage_gid(env)
65
+ xml.label 'virt_image_t'
66
+ end
64
67
  end
65
68
  end
69
+ end.to_xml(
70
+ save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
71
+ Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
72
+ Nokogiri::XML::Node::SaveOptions::FORMAT
73
+ )
74
+ if config.snapshot_pool_name != config.storage_pool_name
75
+ pool_name = config.snapshot_pool_name
76
+ else
77
+ pool_name = config.storage_pool_name
66
78
  end
67
- end.to_xml(
68
- save_with: Nokogiri::XML::Node::SaveOptions::NO_DECLARATION |
69
- Nokogiri::XML::Node::SaveOptions::NO_EMPTY_TAGS |
70
- Nokogiri::XML::Node::SaveOptions::FORMAT
71
- )
72
- if config.snapshot_pool_name != config.storage_pool_name
73
- pool_name = config.snapshot_pool_name
74
- else
75
- pool_name = config.storage_pool_name
79
+ @logger.debug "Using pool #{pool_name} for base box snapshot"
80
+ domain_volume = env[:machine].provider.driver.connection.volumes.create(
81
+ xml: xml,
82
+ pool_name: pool_name
83
+ )
84
+ rescue Fog::Errors::Error => e
85
+ raise Errors::FogDomainVolumeCreateError,
86
+ error_message: e.message
76
87
  end
77
- @logger.debug "Using pool #{pool_name} for base box snapshot"
78
- domain_volume = env[:machine].provider.driver.connection.volumes.create(
79
- xml: xml,
80
- pool_name: pool_name
81
- )
82
- rescue Fog::Errors::Error => e
83
- raise Errors::FogDomainVolumeCreateError,
84
- error_message: e.message
85
88
  end
86
-
87
89
  @app.call(env)
88
90
  end
89
91
  end
@@ -214,9 +214,6 @@ module VagrantPlugins
214
214
  network[:type] = :dhcp
215
215
  end
216
216
 
217
- # do not run configure_networks for tcp tunnel interfaces
218
- next if options.fetch(:tunnel_type, nil)
219
-
220
217
  networks_to_configure << network
221
218
  end
222
219
 
@@ -54,6 +54,8 @@ module VagrantPlugins
54
54
  env[:machine].provider.driver.connection.client
55
55
  )
56
56
 
57
+ current_network = @available_networks.detect { |network| network[:name] == @options[:network_name] }
58
+
57
59
  # Prepare a hash describing network for this specific interface.
58
60
  @interface_network = {
59
61
  name: nil,
@@ -64,11 +66,11 @@ module VagrantPlugins
64
66
  domain_name: nil,
65
67
  ipv6_address: options[:ipv6_address] || nil,
66
68
  ipv6_prefix: options[:ipv6_prefix] || nil,
67
- created: false,
68
- active: false,
69
+ created: current_network.nil? ? false : true,
70
+ active: current_network.nil? ? false : current_network[:active],
69
71
  autostart: options[:autostart] || false,
70
72
  guest_ipv6: @options[:guest_ipv6] || 'yes',
71
- libvirt_network: nil
73
+ libvirt_network: current_network.nil? ? nil : current_network[:libvirt_network]
72
74
  }
73
75
 
74
76
  if @options[:ip]
@@ -255,7 +257,9 @@ module VagrantPlugins
255
257
 
256
258
  # Do we need to create new network?
257
259
  unless @interface_network[:created]
258
- @interface_network[:name] = 'vagrant-private-dhcp'
260
+ @interface_network[:name] = @options[:network_name] ?
261
+ @options[:network_name] :
262
+ 'vagrant-private-dhcp'
259
263
  @interface_network[:network_address] = net_address
260
264
 
261
265
  # Set IP address of network (actually bridge). It will be used as
@@ -297,6 +301,9 @@ module VagrantPlugins
297
301
 
298
302
  @network_ipv6_address = @interface_network[:ipv6_address]
299
303
  @network_ipv6_prefix = @interface_network[:ipv6_prefix]
304
+
305
+ @network_bridge_stp = @options[:bridge_stp].nil? || @options[:bridge_stp] ? 'on' : 'off'
306
+ @network_bridge_delay = @options[:bridge_delay] ? @options[:bridge_delay] : 0
300
307
 
301
308
  @network_forward_mode = @options[:forward_mode]
302
309
  if @options[:forward_device]
@@ -70,7 +70,7 @@ module VagrantPlugins
70
70
 
71
71
  # remove root storage
72
72
  root_disk = domain.volumes.select do |x|
73
- x.name == libvirt_domain.name + '.img'
73
+ x.name == libvirt_domain.name + '.img' if x
74
74
  end.first
75
75
  root_disk.destroy if root_disk
76
76
  end
@@ -11,10 +11,8 @@ module VagrantPlugins
11
11
  end
12
12
 
13
13
  def call(env)
14
- @env = env
15
-
16
14
  # Get the ports we're forwarding
17
- env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config)
15
+ env[:forwarded_ports] = compile_forwarded_ports(env, env[:machine].config)
18
16
 
19
17
  # Warn if we're port forwarding to any privileged ports
20
18
  env[:forwarded_ports].each do |fp|
@@ -28,51 +26,52 @@ module VagrantPlugins
28
26
  # Continue, we need the VM to be booted in order to grab its IP
29
27
  @app.call env
30
28
 
31
- if @env[:forwarded_ports].any?
29
+ if env[:forwarded_ports].any?
32
30
  env[:ui].info I18n.t('vagrant.actions.vm.forward_ports.forwarding')
33
- forward_ports
31
+ forward_ports(env)
34
32
  end
35
33
  end
36
34
 
37
- def forward_ports
38
- @env[:forwarded_ports].each do |fp|
35
+ def forward_ports(env)
36
+ env[:forwarded_ports].each do |fp|
39
37
  message_attributes = {
40
38
  adapter: fp[:adapter] || 'eth0',
41
39
  guest_port: fp[:guest],
42
40
  host_port: fp[:host]
43
41
  }
44
42
 
45
- @env[:ui].info(I18n.t(
43
+ env[:ui].info(I18n.t(
46
44
  'vagrant.actions.vm.forward_ports.forwarding_entry',
47
- message_attributes
45
+ **message_attributes
48
46
  ))
49
47
 
50
- if fp[:protocol] == 'udp'
51
- @env[:ui].warn I18n.t('vagrant_libvirt.warnings.forwarding_udp')
52
- next
53
- end
54
-
55
48
  ssh_pid = redirect_port(
56
- @env[:machine],
49
+ env,
50
+ env[:machine],
57
51
  fp[:host_ip] || '*',
58
52
  fp[:host],
59
- fp[:guest_ip] || @env[:machine].provider.ssh_info[:host],
53
+ fp[:guest_ip] || env[:machine].provider.ssh_info[:host],
60
54
  fp[:guest],
61
55
  fp[:gateway_ports] || false
62
56
  )
63
- store_ssh_pid(fp[:host], ssh_pid)
57
+ store_ssh_pid(env[:machine], fp[:host], ssh_pid)
64
58
  end
65
59
  end
66
60
 
67
61
  private
68
62
 
69
- def compile_forwarded_ports(config)
63
+ def compile_forwarded_ports(env, config)
70
64
  mappings = {}
71
65
 
72
66
  config.vm.networks.each do |type, options|
73
67
  next if options[:disabled]
74
68
 
75
- next unless type == :forwarded_port && options[:id] != 'ssh'
69
+ if options[:protocol] == 'udp'
70
+ env[:ui].warn I18n.t('vagrant_libvirt.warnings.forwarding_udp')
71
+ next
72
+ end
73
+
74
+ next if type != :forwarded_port || ( options[:id] == 'ssh' && !env[:machine].provider_config.forward_ssh_port )
76
75
  if options.fetch(:host_ip, '').to_s.strip.empty?
77
76
  options.delete(:host_ip)
78
77
  end
@@ -82,7 +81,7 @@ module VagrantPlugins
82
81
  mappings.values
83
82
  end
84
83
 
85
- def redirect_port(machine, host_ip, host_port, guest_ip, guest_port,
84
+ def redirect_port(env, machine, host_ip, host_port, guest_ip, guest_port,
86
85
  gateway_ports)
87
86
  ssh_info = machine.ssh_info
88
87
  params = %W(
@@ -114,7 +113,7 @@ module VagrantPlugins
114
113
  if host_port <= 1024
115
114
  @@lock.synchronize do
116
115
  # TODO: add i18n
117
- @env[:ui].info 'Requesting sudo for host port(s) <= 1024'
116
+ env[:ui].info 'Requesting sudo for host port(s) <= 1024'
118
117
  r = system('sudo -v')
119
118
  if r
120
119
  ssh_cmd << 'sudo ' # add sudo prefix
@@ -125,14 +124,15 @@ module VagrantPlugins
125
124
  ssh_cmd << "ssh -n #{options} #{params}"
126
125
 
127
126
  @logger.debug "Forwarding port with `#{ssh_cmd}`"
128
- log_file = ssh_forward_log_file(host_ip, host_port,
129
- guest_ip, guest_port)
127
+ log_file = ssh_forward_log_file(
128
+ env[:machine], host_ip, host_port, guest_ip, guest_port,
129
+ )
130
130
  @logger.info "Logging to #{log_file}"
131
131
  spawn(ssh_cmd, [:out, :err] => [log_file, 'w'], :pgroup => true)
132
132
  end
133
133
 
134
- def ssh_forward_log_file(host_ip, host_port, guest_ip, guest_port)
135
- log_dir = @env[:machine].data_dir.join('logs')
134
+ def ssh_forward_log_file(machine, host_ip, host_port, guest_ip, guest_port)
135
+ log_dir = machine.data_dir.join('logs')
136
136
  log_dir.mkdir unless log_dir.directory?
137
137
  File.join(
138
138
  log_dir,
@@ -141,8 +141,8 @@ module VagrantPlugins
141
141
  )
142
142
  end
143
143
 
144
- def store_ssh_pid(host_port, ssh_pid)
145
- data_dir = @env[:machine].data_dir.join('pids')
144
+ def store_ssh_pid(machine, host_port, ssh_pid)
145
+ data_dir = machine.data_dir.join('pids')
146
146
  data_dir.mkdir unless data_dir.directory?
147
147
 
148
148
  data_dir.join("ssh_#{host_port}.pid").open('w') do |pid_file|
@@ -169,13 +169,12 @@ module VagrantPlugins
169
169
  end
170
170
 
171
171
  def call(env)
172
- @env = env
173
-
174
- if ssh_pids.any?
172
+ pids = ssh_pids(env[:machine])
173
+ if pids.any?
175
174
  env[:ui].info I18n.t(
176
175
  'vagrant.actions.vm.clear_forward_ports.deleting'
177
176
  )
178
- ssh_pids.each do |tag|
177
+ pids.each do |tag|
179
178
  next unless ssh_pid?(tag[:pid])
180
179
  @logger.debug "Killing pid #{tag[:pid]}"
181
180
  kill_cmd = ''
@@ -191,7 +190,7 @@ module VagrantPlugins
191
190
  end
192
191
 
193
192
  @logger.info 'Removing ssh pid files'
194
- remove_ssh_pids
193
+ remove_ssh_pids(env[:machine])
195
194
  else
196
195
  @logger.info 'No ssh pids found'
197
196
  end
@@ -201,9 +200,9 @@ module VagrantPlugins
201
200
 
202
201
  protected
203
202
 
204
- def ssh_pids
205
- glob = @env[:machine].data_dir.join('pids').to_s + '/ssh_*.pid'
206
- @ssh_pids = Dir[glob].map do |file|
203
+ def ssh_pids(machine)
204
+ glob = machine.data_dir.join('pids').to_s + '/ssh_*.pid'
205
+ ssh_pids = Dir[glob].map do |file|
207
206
  {
208
207
  pid: File.read(file).strip.chomp,
209
208
  port: File.basename(file)['ssh_'.length..-1 * ('.pid'.length + 1)].to_i
@@ -217,8 +216,8 @@ module VagrantPlugins
217
216
  `ps -o command= #{pid}`.strip.chomp =~ /ssh/
218
217
  end
219
218
 
220
- def remove_ssh_pids
221
- glob = @env[:machine].data_dir.join('pids').to_s + '/ssh_*.pid'
219
+ def remove_ssh_pids(machine)
220
+ glob = machine.data_dir.join('pids').to_s + '/ssh_*.pid'
222
221
  Dir[glob].each do |file|
223
222
  File.delete file
224
223
  end
@@ -13,24 +13,40 @@ module VagrantPlugins
13
13
  def call(env)
14
14
  env[:ui].info(I18n.t('vagrant_libvirt.halt_domain'))
15
15
 
16
+ timeout = env[:machine].config.vm.graceful_halt_timeout
16
17
  domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
17
18
  raise Errors::NoDomainError if domain.nil?
18
19
 
19
- begin
20
- env[:machine].guest.capability(:halt)
21
- rescue
22
- @logger.info('Trying Libvirt graceful shutdown.')
23
- domain.shutdown
20
+ if env[:force_halt]
21
+ domain.poweroff
22
+ return @app.call(env)
24
23
  end
25
24
 
26
-
27
25
  begin
28
- domain.wait_for(30) do
29
- !ready?
26
+ Timeout.timeout(timeout) do
27
+ begin
28
+ env[:machine].guest.capability(:halt)
29
+ rescue Timeout::Error
30
+ raise
31
+ rescue
32
+ @logger.info('Trying Libvirt graceful shutdown.')
33
+ # Read domain object again
34
+ dom = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
35
+ if dom.state.to_s == 'running'
36
+ dom.shutdown
37
+ end
38
+ end
39
+
40
+ domain.wait_for(timeout) do
41
+ !ready?
42
+ end
30
43
  end
31
- rescue Fog::Errors::TimeoutError
44
+ rescue Timeout::Error
32
45
  @logger.info('VM is still running. Calling force poweroff.')
33
46
  domain.poweroff
47
+ rescue
48
+ @logger.error('Failed to shutdown cleanly. Calling force poweroff.')
49
+ domain.poweroff
34
50
  end
35
51
 
36
52
  @app.call(env)
@@ -16,33 +16,69 @@ module VagrantPlugins
16
16
  end
17
17
 
18
18
  def call(env)
19
- # Verify box metadata for mandatory values.
20
- #
21
- # Virtual size has to be set for allocating space in storage pool.
22
- box_virtual_size = env[:machine].box.metadata['virtual_size']
23
- raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
19
+ # Handle box formats converting between v1 => v2 and ensuring
20
+ # any obsolete settings are rejected.
24
21
 
25
- # Support qcow2 format only for now, but other formats with backing
26
- # store capability should be usable.
27
- box_format = env[:machine].box.metadata['format']
28
- if box_format.nil?
29
- raise Errors::NoBoxFormatSet
30
- elsif box_format != 'qcow2'
31
- raise Errors::WrongBoxFormatSet
22
+ disks = env[:machine].box.metadata.fetch('disks', [])
23
+ if disks.empty?
24
+ # Handle box v1 format
25
+
26
+ # Only qcow2 format is supported in v1, but other formats with backing
27
+ # store capability should be usable.
28
+ box_format = env[:machine].box.metadata['format']
29
+ HandleBoxImage.verify_box_format(box_format)
30
+
31
+ env[:box_volume_number] = 1
32
+ env[:box_volumes] = [{
33
+ :path => HandleBoxImage.get_box_image_path(env[:machine].box, 'box.img'),
34
+ :name => HandleBoxImage.get_volume_name(env[:machine].box, 'box'),
35
+ :virtual_size => HandleBoxImage.get_virtual_size(env),
36
+ :format => box_format,
37
+ }]
38
+ else
39
+ # Handle box v2 format
40
+ # {
41
+ # 'path': '<path-of-file-box>',
42
+ # 'name': '<name-to-use-in-storage>' # optional, will use index
43
+ # }
44
+ #
45
+ env[:box_volume_number] = disks.length()
46
+ target_volumes = Hash[]
47
+ env[:box_volumes] = Array.new(env[:box_volume_number]) { |i|
48
+ raise Errors::BoxFormatMissingAttribute, attribute: "disks[#{i}]['path']" if disks[i]['path'].nil?
49
+
50
+ image_path = HandleBoxImage.get_box_image_path(env[:machine].box, disks[i]['path'])
51
+ format, virtual_size = HandleBoxImage.get_box_disk_settings(image_path)
52
+ volume_name = HandleBoxImage.get_volume_name(
53
+ env[:machine].box,
54
+ disks[i].fetch('name', disks[i]['path'].sub(/#{File.extname(disks[i]['path'])}$/, '')),
55
+ )
56
+
57
+ # allowing name means needing to check that it doesn't cause a clash
58
+ existing = target_volumes[volume_name]
59
+ if !existing.nil?
60
+ raise Errors::BoxFormatDuplicateVolume, volume: volume_name, new_disk: "disks[#{i}]", orig_disk: "disks[#{existing}]"
61
+ end
62
+ target_volumes[volume_name] = i
63
+
64
+ {
65
+ :path => image_path,
66
+ :name => volume_name,
67
+ :virtual_size => virtual_size.to_i,
68
+ :format => HandleBoxImage.verify_box_format(format)
69
+ }
70
+ }
32
71
  end
33
72
 
34
73
  # Get config options
35
74
  config = env[:machine].provider_config
36
- box_image_file = env[:machine].box.directory.join('box.img').to_s
37
- env[:box_volume_name] = env[:machine].box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
38
- env[:box_volume_name] << "_vagrant_box_image_#{
39
- begin
40
- env[:machine].box.version.to_s
41
- rescue
42
- ''
43
- end}.img"
75
+ box_image_files = []
76
+ env[:box_volumes].each do |d|
77
+ box_image_files.push(d[:path])
78
+ end
44
79
 
45
80
  # Override box_virtual_size
81
+ box_virtual_size = env[:box_volumes][0][:virtual_size]
46
82
  if config.machine_virtual_size
47
83
  if config.machine_virtual_size < box_virtual_size
48
84
  # Warn that a virtual size less than the box metadata size
@@ -57,77 +93,129 @@ module VagrantPlugins
57
93
  end
58
94
  end
59
95
  # save for use by later actions
60
- env[:box_virtual_size] = box_virtual_size
96
+ env[:box_volumes][0][:virtual_size] = box_virtual_size
61
97
 
62
98
  # while inside the synchronize block take care not to call the next
63
99
  # action in the chain, as must exit this block first to prevent
64
100
  # locking all subsequent actions as well.
65
101
  @@lock.synchronize do
66
- # Don't continue if image already exists in storage pool.
67
- box_volume = env[:machine].provider.driver.connection.volumes.all(
68
- name: env[:box_volume_name]
69
- ).first
70
- break if box_volume && box_volume.id
71
-
72
- # Box is not available as a storage pool volume. Create and upload
73
- # it as a copy of local box image.
74
- env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
75
-
76
- # Create new volume in storage pool
77
- unless File.exist?(box_image_file)
78
- raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
102
+ env[:box_volumes].each_index do |i|
103
+ # Don't continue if image already exists in storage pool.
104
+ box_volume = env[:machine].provider.driver.connection.volumes.all(
105
+ name: env[:box_volumes][i][:name]
106
+ ).first
107
+ next if box_volume && box_volume.id
108
+
109
+ send_box_image(env, config, box_image_files[i], env[:box_volumes][i])
79
110
  end
80
- box_image_size = File.size(box_image_file) # B
81
- message = "Creating volume #{env[:box_volume_name]}"
82
- message << " in storage pool #{config.storage_pool_name}."
83
- @logger.info(message)
111
+ end
112
+
113
+ @app.call(env)
114
+ end
84
115
 
85
- @storage_volume_uid = storage_uid env
86
- @storage_volume_gid = storage_gid env
116
+ protected
87
117
 
118
+ def self.get_volume_name(box, name)
119
+ vol_name = box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
120
+ vol_name << "_vagrant_box_image_#{
88
121
  begin
89
- fog_volume = env[:machine].provider.driver.connection.volumes.create(
90
- name: env[:box_volume_name],
91
- allocation: "#{box_image_size / 1024 / 1024}M",
92
- capacity: "#{box_virtual_size}G",
93
- format_type: box_format,
94
- owner: @storage_volume_uid,
95
- group: @storage_volume_gid,
96
- pool_name: config.storage_pool_name
97
- )
98
- rescue Fog::Errors::Error => e
99
- raise Errors::FogCreateVolumeError,
100
- error_message: e.message
122
+ box.version.to_s
123
+ rescue
124
+ ''
101
125
  end
126
+ }_#{name.dup.gsub('/', '-SLASH-')}.img"
127
+ end
102
128
 
103
- # Upload box image to storage pool
104
- ret = upload_image(box_image_file, config.storage_pool_name,
105
- env[:box_volume_name], env) do |progress|
106
- rewriting(env[:ui]) do |ui|
107
- ui.clear_line
108
- ui.report_progress(progress, box_image_size, false)
109
- end
110
- end
129
+ def self.get_virtual_size(env)
130
+ # Virtual size has to be set for allocating space in storage pool.
131
+ box_virtual_size = env[:machine].box.metadata['virtual_size']
132
+ raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
133
+ return box_virtual_size
134
+ end
111
135
 
112
- # Clear the line one last time since the progress meter doesn't
113
- # disappear immediately.
114
- rewriting(env[:ui]) {|ui| ui.clear_line}
115
-
116
- # If upload failed or was interrupted, remove created volume from
117
- # storage pool.
118
- if env[:interrupted] || !ret
119
- begin
120
- fog_volume.destroy
121
- rescue
122
- nil
123
- end
136
+ def self.get_box_image_path(box, box_name)
137
+ return box.directory.join(box_name).to_s
138
+ end
139
+
140
+ def self.verify_box_format(box_format, disk_index=nil)
141
+ if box_format.nil?
142
+ raise Errors::NoBoxFormatSet
143
+ elsif box_format != 'qcow2'
144
+ if disk_index.nil?
145
+ raise Errors::WrongBoxFormatSet
146
+ else
147
+ raise Errors::WrongDiskFormatSet,
148
+ disk_index: disk_index
124
149
  end
125
150
  end
151
+ return box_format
152
+ end
126
153
 
127
- @app.call(env)
154
+ def self.get_box_disk_settings(image_path)
155
+ stdout, stderr, status = Open3.capture3('qemu-img', 'info', image_path)
156
+ if !status.success?
157
+ raise Errors::BadBoxImage, image: image_path, out: stdout, err: stderr
158
+ end
159
+
160
+ image_info_lines = stdout.split("\n")
161
+ format = image_info_lines.find { |l| l.start_with?('file format:') }.split(' ')[2]
162
+ virtual_size = image_info_lines.find { |l| l.start_with?('virtual size:') }.split(' ')[2]
163
+
164
+ return format, virtual_size
128
165
  end
129
166
 
130
- protected
167
+ def send_box_image(env, config, box_image_file, box_volume)
168
+ # Box is not available as a storage pool volume. Create and upload
169
+ # it as a copy of local box image.
170
+ env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
171
+
172
+ # Create new volume in storage pool
173
+ unless File.exist?(box_image_file)
174
+ raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
175
+ end
176
+ box_image_size = File.size(box_image_file) # B
177
+ message = "Creating volume #{box_volume[:name]}"
178
+ message << " in storage pool #{config.storage_pool_name}."
179
+ @logger.info(message)
180
+
181
+ begin
182
+ fog_volume = env[:machine].provider.driver.connection.volumes.create(
183
+ name: box_volume[:name],
184
+ allocation: "#{box_image_size / 1024 / 1024}M",
185
+ capacity: "#{box_volume[:virtual_size]}G",
186
+ format_type: box_volume[:format],
187
+ owner: storage_uid(env),
188
+ group: storage_gid(env),
189
+ pool_name: config.storage_pool_name
190
+ )
191
+ rescue Fog::Errors::Error => e
192
+ raise Errors::FogCreateVolumeError,
193
+ error_message: e.message
194
+ end
195
+
196
+ # Upload box image to storage pool
197
+ ret = upload_image(box_image_file, config.storage_pool_name,
198
+ box_volume[:name], env) do |progress|
199
+ rewriting(env[:ui]) do |ui|
200
+ ui.clear_line
201
+ ui.report_progress(progress, box_image_size, false)
202
+ end
203
+ end
204
+
205
+ # Clear the line one last time since the progress meter doesn't
206
+ # disappear immediately.
207
+ rewriting(env[:ui]) {|ui| ui.clear_line}
208
+
209
+ # If upload failed or was interrupted, remove created volume from
210
+ # storage pool.
211
+ if env[:interrupted] || !ret
212
+ begin
213
+ fog_volume.destroy
214
+ rescue
215
+ nil
216
+ end
217
+ end
218
+ end
131
219
 
132
220
  # Fog Libvirt currently doesn't support uploading images to storage
133
221
  # pool volumes. Use ruby-libvirt client instead.