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
@@ -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.