vagrant-libvirt 0.3.0 → 0.4.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +170 -17
  3. data/lib/vagrant-libvirt/action/create_domain.rb +30 -9
  4. data/lib/vagrant-libvirt/action/forward_ports.rb +1 -1
  5. data/lib/vagrant-libvirt/action/package_domain.rb +2 -1
  6. data/lib/vagrant-libvirt/action/start_domain.rb +86 -29
  7. data/lib/vagrant-libvirt/action/wait_till_up.rb +7 -27
  8. data/lib/vagrant-libvirt/config.rb +202 -41
  9. data/lib/vagrant-libvirt/driver.rb +46 -31
  10. data/lib/vagrant-libvirt/provider.rb +2 -9
  11. data/lib/vagrant-libvirt/templates/domain.xml.erb +29 -5
  12. data/lib/vagrant-libvirt/version +1 -1
  13. data/lib/vagrant-libvirt/version.rb +57 -9
  14. data/spec/spec_helper.rb +28 -2
  15. data/spec/support/libvirt_context.rb +2 -0
  16. data/spec/support/sharedcontext.rb +4 -0
  17. data/spec/unit/action/create_domain_spec.rb +110 -35
  18. data/spec/unit/action/create_domain_spec/{default_storage_pool.xml → default_system_storage_pool.xml} +0 -0
  19. data/spec/unit/action/create_domain_spec/default_user_storage_pool.xml +17 -0
  20. data/spec/unit/action/start_domain_spec.rb +183 -1
  21. data/spec/unit/action/start_domain_spec/clock_timer_rtc.xml +50 -0
  22. data/spec/unit/action/start_domain_spec/default.xml +2 -2
  23. data/spec/unit/action/start_domain_spec/default_added_tpm_path.xml +48 -0
  24. data/spec/unit/action/start_domain_spec/default_added_tpm_version.xml +48 -0
  25. data/spec/unit/action/wait_till_up_spec.rb +14 -9
  26. data/spec/unit/config_spec.rb +392 -127
  27. data/spec/unit/provider_spec.rb +11 -0
  28. data/spec/unit/templates/domain_all_settings.xml +6 -3
  29. data/spec/unit/templates/domain_custom_cpu_model.xml +2 -1
  30. data/spec/unit/templates/domain_defaults.xml +2 -1
  31. data/spec/unit/templates/domain_spec.rb +80 -2
  32. data/spec/unit/templates/tpm/version_1.2.xml +54 -0
  33. data/spec/unit/templates/tpm/version_2.0.xml +53 -0
  34. metadata +74 -17
@@ -50,8 +50,9 @@ module VagrantPlugins
50
50
  # remove hw association with interface
51
51
  # working for centos with lvs default disks
52
52
  options = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPTIONS', '')
53
- operations = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPERATIONS', 'defaults,-ssh-userdir')
53
+ operations = ENV.fetch('VAGRANT_LIBVIRT_VIRT_SYSPREP_OPERATIONS', 'defaults,-ssh-userdir,-customize')
54
54
  `virt-sysprep --no-logfile --operations #{operations} -a #{@tmp_img} #{options}`
55
+ `virt-sparsify --in-place #{@tmp_img}`
55
56
  # add any user provided file
56
57
  extra = ''
57
58
  @tmp_include = @tmp_dir + '/_include'
@@ -37,12 +37,16 @@ module VagrantPlugins
37
37
  xml_descr = REXML::Document.new descr
38
38
  descr_changed = false
39
39
 
40
+ # For outputting XML for comparison
41
+ formatter = REXML::Formatters::Pretty.new
42
+
40
43
  # additional disk bus
41
44
  config.disks.each do |disk|
42
45
  device = disk[:device]
43
46
  bus = disk[:bus]
44
47
  REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="' + device + '"]') do |disk_target|
45
48
  next unless disk_target.attributes['bus'] != bus
49
+ @logger.debug "disk #{device} bus updated from '#{disk_target.attributes['bus']}' to '#{bus}'"
46
50
  descr_changed = true
47
51
  disk_target.attributes['bus'] = bus
48
52
  disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
@@ -52,6 +56,7 @@ module VagrantPlugins
52
56
  # disk_bus
53
57
  REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="vda"]') do |disk_target|
54
58
  next unless disk_target.attributes['bus'] != config.disk_bus
59
+ @logger.debug "domain disk bus updated from '#{disk_target.attributes['bus']}' to '#{bus}'"
55
60
  descr_changed = true
56
61
  disk_target.attributes['bus'] = config.disk_bus
57
62
  disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
@@ -61,6 +66,7 @@ module VagrantPlugins
61
66
  unless config.nic_model_type.nil?
62
67
  REXML::XPath.each(xml_descr, '/domain/devices/interface/model') do |iface_model|
63
68
  if iface_model.attributes['type'] != config.nic_model_type
69
+ @logger.debug "network type updated from '#{iface_model.attributes['type']}' to '#{config.nic_model_type}'"
64
70
  descr_changed = true
65
71
  iface_model.attributes['type'] = config.nic_model_type
66
72
  end
@@ -68,7 +74,9 @@ module VagrantPlugins
68
74
  end
69
75
 
70
76
  # vCpu count
71
- if config.cpus.to_i != libvirt_domain.num_vcpus(0)
77
+ vcpus_count = libvirt_domain.num_vcpus(0)
78
+ if config.cpus.to_i != vcpus_count
79
+ @logger.debug "cpu count updated from '#{vcpus_count}' to '#{config.cpus}'"
72
80
  descr_changed = true
73
81
  REXML::XPath.first(xml_descr, '/domain/vcpu').text = config.cpus
74
82
  end
@@ -76,11 +84,13 @@ module VagrantPlugins
76
84
  # cpu_mode
77
85
  cpu = REXML::XPath.first(xml_descr, '/domain/cpu')
78
86
  if cpu.nil?
87
+ @logger.debug "cpu_mode updated from not set to '#{config.cpu_mode}'"
79
88
  descr_changed = true
80
89
  cpu = REXML::Element.new('cpu', REXML::XPath.first(xml_descr, '/domain'))
81
90
  cpu.attributes['mode'] = config.cpu_mode
82
91
  else
83
92
  if cpu.attributes['mode'] != config.cpu_mode
93
+ @logger.debug "cpu_mode updated from '#{cpu.attributes['mode']}' to '#{config.cpu_mode}'"
84
94
  descr_changed = true
85
95
  cpu.attributes['mode'] = config.cpu_mode
86
96
  end
@@ -89,16 +99,19 @@ module VagrantPlugins
89
99
  if config.cpu_mode != 'host-passthrough'
90
100
  cpu_model = REXML::XPath.first(xml_descr, '/domain/cpu/model')
91
101
  if cpu_model.nil?
102
+ @logger.debug "cpu_model updated from not set to '#{config.cpu_model}'"
92
103
  descr_changed = true
93
104
  cpu_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/cpu'))
94
105
  cpu_model.attributes['fallback'] = 'allow'
95
106
  cpu_model.text = config.cpu_model
96
107
  else
97
- if cpu_model.text.strip != config.cpu_model.strip
108
+ if (cpu_model.text or '').strip != config.cpu_model.strip
109
+ @logger.debug "cpu_model text updated from #{cpu_model.text} to '#{config.cpu_model}'"
98
110
  descr_changed = true
99
111
  cpu_model.text = config.cpu_model
100
112
  end
101
113
  if cpu_model.attributes['fallback'] != config.cpu_fallback
114
+ @logger.debug "cpu_model fallback attribute updated from #{cpu_model.attributes['fallback']} to '#{config.cpu_fallback}'"
102
115
  descr_changed = true
103
116
  cpu_model.attributes['fallback'] = config.cpu_fallback
104
117
  end
@@ -107,12 +120,14 @@ module VagrantPlugins
107
120
  svm_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="svm"]')
108
121
  if config.nested
109
122
  if vmx_feature.nil?
123
+ @logger.debug "nested mode enabled from unset by setting cpu vmx feature"
110
124
  descr_changed = true
111
125
  vmx_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
112
126
  vmx_feature.attributes['policy'] = 'optional'
113
127
  vmx_feature.attributes['name'] = 'vmx'
114
128
  end
115
129
  if svm_feature.nil?
130
+ @logger.debug "nested mode enabled from unset by setting cpu svm feature"
116
131
  descr_changed = true
117
132
  svm_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
118
133
  svm_feature.attributes['policy'] = 'optional'
@@ -120,16 +135,19 @@ module VagrantPlugins
120
135
  end
121
136
  else
122
137
  unless vmx_feature.nil?
138
+ @logger.debug "nested mode disabled for cpu by removing vmx feature"
123
139
  descr_changed = true
124
140
  cpu.delete_element(vmx_feature)
125
141
  end
126
142
  unless svm_feature.nil?
143
+ @logger.debug "nested mode disabled for cpu by removing svm feature"
127
144
  descr_changed = true
128
145
  cpu.delete_element(svm_feature)
129
146
  end
130
147
  end
131
148
  elsif config.numa_nodes == nil
132
149
  unless cpu.elements.to_a.empty?
150
+ @logger.debug "switching cpu_mode to host-passthrough and removing emulated cpu features"
133
151
  descr_changed = true
134
152
  cpu.elements.each do |elem|
135
153
  cpu.delete_element(elem)
@@ -137,6 +155,34 @@ module VagrantPlugins
137
155
  end
138
156
  end
139
157
 
158
+ # Clock
159
+ clock = REXML::XPath.first(xml_descr, '/domain/clock')
160
+ if clock.attributes['offset'] != config.clock_offset
161
+ @logger.debug "clock offset changed"
162
+ descr_changed = true
163
+ clock.attributes['offset'] = config.clock_offset
164
+ end
165
+
166
+ # clock timers - because timers can be added/removed, just rebuild and then compare
167
+ if !config.clock_timers.empty? || clock.has_elements?
168
+ oldclock = ''
169
+ formatter.write(REXML::XPath.first(xml_descr, '/domain/clock'), oldclock)
170
+ clock.delete_element('//timer')
171
+ config.clock_timers.each do |clock_timer|
172
+ timer = REXML::Element.new('timer', clock)
173
+ clock_timer.each do |attr, value|
174
+ timer.attributes[attr.to_s] = value
175
+ end
176
+ end
177
+
178
+ newclock = ''
179
+ formatter.write(clock, newclock)
180
+ unless newclock.eql? oldclock
181
+ @logger.debug "clock timers config changed"
182
+ descr_changed = true
183
+ end
184
+ end
185
+
140
186
  # Graphics
141
187
  graphics = REXML::XPath.first(xml_descr, '/domain/devices/graphics')
142
188
  if config.graphics_type != 'none'
@@ -178,31 +224,31 @@ module VagrantPlugins
178
224
  end
179
225
 
180
226
  # TPM
181
- if config.tpm_path
182
- raise Errors::FogCreateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/'
227
+ if [config.tpm_path, config.tpm_version].any?
228
+ if config.tpm_path
229
+ raise Errors::FogCreateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/'
230
+ end
183
231
 
184
- tpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm')
185
- if tpm.nil?
232
+ # just build the tpm element every time
233
+ # check at the end if it is different
234
+ oldtpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm')
235
+ REXML::XPath.first(xml_descr, '/domain/devices').delete_element("tpm")
236
+ newtpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices'))
237
+
238
+ newtpm.attributes['model'] = config.tpm_model
239
+ backend = newtpm.add_element('backend')
240
+ backend.attributes['type'] = config.tpm_type
241
+
242
+ case config.tpm_type
243
+ when 'emulator'
244
+ backend.attributes['version'] = config.tpm_version
245
+ when 'passthrough'
246
+ backend.add_element('device').attributes['path'] = config.tpm_path
247
+ end
248
+
249
+ unless "'#{newtpm}'".eql? "'#{oldtpm}'"
250
+ @logger.debug "tpm config changed"
186
251
  descr_changed = true
187
- tpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices/tpm/model'))
188
- tpm.attributes['model'] = config.tpm_model
189
- tpm_backend_type = tpm.add_element('backend')
190
- tpm_backend_type.attributes['type'] = config.tpm_type
191
- tpm_device_path = tpm_backend_type.add_element('device')
192
- tpm_device_path.attributes['path'] = config.tpm_path
193
- else
194
- if tpm.attributes['model'] != config.tpm_model
195
- descr_changed = true
196
- tpm.attributes['model'] = config.tpm_model
197
- end
198
- if tpm.elements['backend'].attributes['type'] != config.tpm_type
199
- descr_changed = true
200
- tpm.elements['backend'].attributes['type'] = config.tpm_type
201
- end
202
- if tpm.elements['backend'].elements['device'].attributes['path'] != config.tpm_path
203
- descr_changed = true
204
- tpm.elements['backend'].elements['device'].attributes['path'] = config.tpm_path
205
- end
206
252
  end
207
253
  end
208
254
 
@@ -210,16 +256,21 @@ module VagrantPlugins
210
256
  video = REXML::XPath.first(xml_descr, '/domain/devices/video')
211
257
  if !video.nil? && (config.graphics_type == 'none')
212
258
  # graphics_type = none, video devices are removed since there is no possible output
259
+ @logger.debug "deleting video elements as config.graphics_type is none"
213
260
  descr_changed = true
214
261
  video.parent.delete_element(video)
215
262
  else
216
263
  video_model = REXML::XPath.first(xml_descr, '/domain/devices/video/model')
217
264
  if video_model.nil?
265
+ @logger.debug "video updated from not set to type '#{config.video_type}' and vram '#{config.video_vram}'"
266
+ descr_changed = true
218
267
  video_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/devices/video'))
219
268
  video_model.attributes['type'] = config.video_type
220
269
  video_model.attributes['vram'] = config.video_vram
221
270
  else
222
- if video_model.attributes['type'] != config.video_type || video_model.attributes['vram'] != config.video_vram
271
+ if video_model.attributes['type'] != config.video_type || video_model.attributes['vram'] != config.video_vram.to_s
272
+ @logger.debug "video type updated from '#{video_model.attributes['type']}' to '#{config.video_type}'"
273
+ @logger.debug "video vram updated from '#{video_model.attributes['vram']}' to '#{config.video_vram}'"
223
274
  descr_changed = true
224
275
  video_model.attributes['type'] = config.video_type
225
276
  video_model.attributes['vram'] = config.video_vram
@@ -237,11 +288,13 @@ module VagrantPlugins
237
288
  if config.dtb
238
289
  dtb = REXML::XPath.first(xml_descr, '/domain/os/dtb')
239
290
  if dtb.nil?
291
+ @logger.debug "dtb updated from not set to '#{config.dtb}'"
240
292
  descr_changed = true
241
293
  dtb = REXML::Element.new('dtb', REXML::XPath.first(xml_descr, '/domain/os'))
242
294
  dtb.text = config.dtb
243
295
  else
244
- if dtb.text != config.dtb
296
+ if (dtb.text or '') != config.dtb
297
+ @logger.debug "dtb updated from '#{dtb.text}' to '#{config.dtb}'"
245
298
  descr_changed = true
246
299
  dtb.text = config.dtb
247
300
  end
@@ -252,11 +305,13 @@ module VagrantPlugins
252
305
  if config.kernel
253
306
  kernel = REXML::XPath.first(xml_descr, '/domain/os/kernel')
254
307
  if kernel.nil?
308
+ @logger.debug "kernel updated from not set to '#{config.kernel}'"
255
309
  descr_changed = true
256
310
  kernel = REXML::Element.new('kernel', REXML::XPath.first(xml_descr, '/domain/os'))
257
311
  kernel.text = config.kernel
258
312
  else
259
- if kernel.text != config.kernel
313
+ if (kernel.text or '').strip != config.kernel
314
+ @logger.debug "kernel updated from '#{kernel.text}' to '#{config.kernel}'"
260
315
  descr_changed = true
261
316
  kernel.text = config.kernel
262
317
  end
@@ -265,11 +320,13 @@ module VagrantPlugins
265
320
  if config.initrd
266
321
  initrd = REXML::XPath.first(xml_descr, '/domain/os/initrd')
267
322
  if initrd.nil?
323
+ @logger.debug "initrd updated from not set to '#{config.initrd}'"
268
324
  descr_changed = true
269
325
  initrd = REXML::Element.new('initrd', REXML::XPath.first(xml_descr, '/domain/os'))
270
326
  initrd.text = config.initrd
271
327
  else
272
- if initrd.text != config.initrd
328
+ if (initrd.text or '').strip != config.initrd
329
+ @logger.debug "initrd updated from '#{initrd.text}' to '#{config.initrd}'"
273
330
  descr_changed = true
274
331
  initrd.text = config.initrd
275
332
  end
@@ -21,7 +21,7 @@ module VagrantPlugins
21
21
  env[:metrics] ||= {}
22
22
 
23
23
  # Get domain object
24
- domain = env[:machine].provider.driver.get_domain(env[:machine].id.to_s)
24
+ domain = env[:machine].provider.driver.get_domain(env[:machine])
25
25
  if domain.nil?
26
26
  raise Errors::NoDomainError,
27
27
  error_message: "Domain #{env[:machine].id} not found"
@@ -34,33 +34,13 @@ module VagrantPlugins
34
34
  @logger.debug("Searching for IP for MAC address: #{domain.mac}")
35
35
  env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ip'))
36
36
 
37
- if env[:machine].provider_config.qemu_use_session
38
- env[:metrics]['instance_ip_time'] = Util::Timer.time do
39
- retryable(on: Fog::Errors::TimeoutError, tries: 300) do
40
- # just return if interrupted and let the warden call recover
41
- return if env[:interrupted]
37
+ env[:metrics]['instance_ip_time'] = Util::Timer.time do
38
+ retryable(on: Fog::Errors::TimeoutError, tries: 300) do
39
+ # just return if interrupted and let the warden call recover
40
+ return if env[:interrupted]
42
41
 
43
- # Wait for domain to obtain an ip address
44
- domain.wait_for(2) do
45
- env[:ip_address] = env[:machine].provider.driver.get_ipaddress_system(domain.mac)
46
- !env[:ip_address].nil?
47
- end
48
- end
49
- end
50
- else
51
- env[:metrics]['instance_ip_time'] = Util::Timer.time do
52
- retryable(on: Fog::Errors::TimeoutError, tries: 300) do
53
- # just return if interrupted and let the warden call recover
54
- return if env[:interrupted]
55
-
56
- # Wait for domain to obtain an ip address
57
- domain.wait_for(2) do
58
- addresses.each_pair do |_type, ip|
59
- env[:ip_address] = ip[0] unless ip[0].nil?
60
- end
61
- !env[:ip_address].nil?
62
- end
63
- end
42
+ # Wait for domain to obtain an ip address
43
+ env[:ip_address] = env[:machine].provider.driver.get_domain_ipaddress(env[:machine], domain)
64
44
  end
65
45
  end
66
46
 
@@ -37,6 +37,8 @@ module VagrantPlugins
37
37
  # ID SSH key file
38
38
  attr_accessor :id_ssh_key_file
39
39
 
40
+ attr_accessor :proxy_command
41
+
40
42
  # Libvirt storage pool name, where box image and instance snapshots will
41
43
  # be stored.
42
44
  attr_accessor :storage_pool_name
@@ -84,6 +86,8 @@ module VagrantPlugins
84
86
  attr_accessor :shares
85
87
  attr_accessor :features
86
88
  attr_accessor :features_hyperv
89
+ attr_accessor :clock_offset
90
+ attr_accessor :clock_timers
87
91
  attr_accessor :numa_nodes
88
92
  attr_accessor :loader
89
93
  attr_accessor :nvram
@@ -93,9 +97,10 @@ module VagrantPlugins
93
97
  attr_accessor :machine_virtual_size
94
98
  attr_accessor :disk_bus
95
99
  attr_accessor :disk_device
100
+ attr_accessor :disk_driver_opts
96
101
  attr_accessor :nic_model_type
97
102
  attr_accessor :nested
98
- attr_accessor :volume_cache
103
+ attr_accessor :volume_cache # deprecated, kept for backwards compatibility; use disk_driver
99
104
  attr_accessor :kernel
100
105
  attr_accessor :cmd_line
101
106
  attr_accessor :initrd
@@ -117,6 +122,13 @@ module VagrantPlugins
117
122
  attr_accessor :tpm_model
118
123
  attr_accessor :tpm_type
119
124
  attr_accessor :tpm_path
125
+ attr_accessor :tpm_version
126
+
127
+ # Configure the memballoon
128
+ attr_accessor :memballoon_enabled
129
+ attr_accessor :memballoon_model
130
+ attr_accessor :memballoon_pci_bus
131
+ attr_accessor :memballoon_pci_slot
120
132
 
121
133
  # Sets the max number of NICs that can be created
122
134
  # Default set to 8. Don't change the default unless you know
@@ -181,6 +193,8 @@ module VagrantPlugins
181
193
  @username = UNSET_VALUE
182
194
  @password = UNSET_VALUE
183
195
  @id_ssh_key_file = UNSET_VALUE
196
+ @socket = UNSET_VALUE
197
+ @proxy_command = UNSET_VALUE
184
198
  @storage_pool_name = UNSET_VALUE
185
199
  @snapshot_pool_name = UNSET_VALUE
186
200
  @random_hostname = UNSET_VALUE
@@ -215,6 +229,8 @@ module VagrantPlugins
215
229
  @shares = UNSET_VALUE
216
230
  @features = UNSET_VALUE
217
231
  @features_hyperv = UNSET_VALUE
232
+ @clock_offset = UNSET_VALUE
233
+ @clock_timers = []
218
234
  @numa_nodes = UNSET_VALUE
219
235
  @loader = UNSET_VALUE
220
236
  @nvram = UNSET_VALUE
@@ -223,6 +239,7 @@ module VagrantPlugins
223
239
  @machine_virtual_size = UNSET_VALUE
224
240
  @disk_bus = UNSET_VALUE
225
241
  @disk_device = UNSET_VALUE
242
+ @disk_driver_opts = {}
226
243
  @nic_model_type = UNSET_VALUE
227
244
  @nested = UNSET_VALUE
228
245
  @volume_cache = UNSET_VALUE
@@ -245,6 +262,12 @@ module VagrantPlugins
245
262
  @tpm_model = UNSET_VALUE
246
263
  @tpm_type = UNSET_VALUE
247
264
  @tpm_path = UNSET_VALUE
265
+ @tpm_version = UNSET_VALUE
266
+
267
+ @memballoon_enabled = UNSET_VALUE
268
+ @memballoon_model = UNSET_VALUE
269
+ @memballoon_pci_bus = UNSET_VALUE
270
+ @memballoon_pci_slot = UNSET_VALUE
248
271
 
249
272
  @nic_adapter_count = UNSET_VALUE
250
273
 
@@ -379,6 +402,25 @@ module VagrantPlugins
379
402
  state: options[:state])
380
403
  end
381
404
 
405
+ def clock_timer(options = {})
406
+ if options[:name].nil?
407
+ raise 'Clock timer name must be specified'
408
+ end
409
+
410
+ options.each do |key, value|
411
+ case key
412
+ when :name, :track, :tickpolicy, :frequency, :mode, :present
413
+ if value.nil?
414
+ raise "Value of timer option #{key} is nil"
415
+ end
416
+ else
417
+ raise "Unknown clock timer option: #{key}"
418
+ end
419
+ end
420
+
421
+ @clock_timers.push(options.dup)
422
+ end
423
+
382
424
  def cputopology(options = {})
383
425
  if options[:sockets].nil? || options[:cores].nil? || options[:threads].nil?
384
426
  raise 'CPU topology must have all of sockets, cores and threads specified'
@@ -551,6 +593,12 @@ module VagrantPlugins
551
593
  @smartcard_dev[:source_service] = options[:source_service] if @smartcard_dev[:type] == 'tcp'
552
594
  end
553
595
 
596
+ # Disk driver options for primary disk
597
+ def disk_driver(options = {})
598
+ supported_opts = [:cache, :io, :copy_on_read, :discard, :detect_zeroes]
599
+ @disk_driver_opts = options.select { |k,_| supported_opts.include? k }
600
+ end
601
+
554
602
  # NOTE: this will run twice for each time it's needed- keep it idempotent
555
603
  def storage(storage_type, options = {})
556
604
  if storage_type == :file
@@ -605,6 +653,10 @@ module VagrantPlugins
605
653
  allow_existing: options[:allow_existing],
606
654
  shareable: options[:shareable],
607
655
  serial: options[:serial],
656
+ io: options[:io],
657
+ copy_on_read: options[:copy_on_read],
658
+ discard: options[:discard],
659
+ detect_zeroes: options[:detect_zeroes],
608
660
  pool: options[:pool], # overrides storage_pool setting for additional disks
609
661
  wwn: options[:wwn],
610
662
  }
@@ -624,15 +676,25 @@ module VagrantPlugins
624
676
  @qemu_env.merge!(options)
625
677
  end
626
678
 
679
+ def _default_uri
680
+ # Determine if any settings except driver provided explicitly, if not
681
+ # and the LIBVIRT_DEFAULT_URI var is set, use that.
682
+ #
683
+ # Skipping driver because that may be set on individual boxes rather
684
+ # than by the user.
685
+ if [
686
+ @connect_via_ssh, @host, @username, @password,
687
+ @id_ssh_key_file, @qemu_use_session, @socket,
688
+ ].none?{ |v| v != UNSET_VALUE }
689
+ if ENV.fetch('LIBVIRT_DEFAULT_URI', '') != ""
690
+ @uri = ENV['LIBVIRT_DEFAULT_URI']
691
+ end
692
+ end
693
+ end
694
+
627
695
  # code to generate URI from from either the LIBVIRT_URI environment
628
696
  # variable or a config moved out of the connect action
629
697
  def _generate_uri(qemu_use_session)
630
-
631
- # If the LIBVIRT_DEFAULT_URI var is set, we'll use that
632
- if ENV.fetch('LIBVIRT_DEFAULT_URI', '') != ""
633
- return ENV['LIBVIRT_DEFAULT_URI']
634
- end
635
-
636
698
  # builds the Libvirt connection URI from the given driver config
637
699
  # Setup connection uri.
638
700
  uri = @driver.dup
@@ -652,30 +714,34 @@ module VagrantPlugins
652
714
  uri = 'qemu' # use QEMU uri for KVM domain type
653
715
  end
654
716
 
655
- if @connect_via_ssh
717
+ # turn on ssh if an ssh key file is explicitly provided
718
+ if @connect_via_ssh == UNSET_VALUE && @id_ssh_key_file && @id_ssh_key_file != UNSET_VALUE
719
+ @connect_via_ssh = true
720
+ end
721
+
722
+ params = {}
723
+
724
+ if @connect_via_ssh == true
725
+ finalize_id_ssh_key_file
726
+
656
727
  uri << '+ssh://'
657
- uri << @username + '@' if @username
728
+ uri << @username + '@' if @username && @username != UNSET_VALUE
729
+
730
+ uri << ( @host && @host != UNSET_VALUE ? @host : 'localhost' )
658
731
 
659
- uri << ( @host ? @host : 'localhost' )
732
+ params['no_verify'] = '1'
733
+ params['keyfile'] = @id_ssh_key_file if @id_ssh_key_file
660
734
  else
661
735
  uri << '://'
662
- uri << @host if @host
736
+ uri << @host if @host && @host != UNSET_VALUE
663
737
  end
664
738
 
665
739
  uri << virt_path
666
740
 
667
- params = {'no_verify' => '1'}
668
-
669
- if @id_ssh_key_file
670
- # set ssh key for access to Libvirt host
671
- # if no slash, prepend $HOME/.ssh/
672
- @id_ssh_key_file.prepend("#{ENV['HOME']}/.ssh/") if @id_ssh_key_file !~ /\A\//
673
- params['keyfile'] = @id_ssh_key_file
674
- end
675
741
  # set path to Libvirt socket
676
742
  params['socket'] = @socket if @socket
677
743
 
678
- uri << "?" + params.map{|pair| pair.join('=')}.join('&')
744
+ uri << "?" + params.map{|pair| pair.join('=')}.join('&') if !params.empty?
679
745
  uri
680
746
  end
681
747
 
@@ -688,12 +754,22 @@ module VagrantPlugins
688
754
  end
689
755
 
690
756
  def finalize!
757
+ _default_uri if @uri == UNSET_VALUE
758
+
759
+ # settings which _generate_uri
691
760
  @driver = 'kvm' if @driver == UNSET_VALUE
692
- @host = nil if @host == UNSET_VALUE
693
- @connect_via_ssh = false if @connect_via_ssh == UNSET_VALUE
694
- @username = nil if @username == UNSET_VALUE
695
761
  @password = nil if @password == UNSET_VALUE
696
- @id_ssh_key_file = 'id_rsa' if @id_ssh_key_file == UNSET_VALUE
762
+ @socket = nil if @socket == UNSET_VALUE
763
+
764
+ # If uri isn't set then let's build one from various sources.
765
+ # Default to passing false for qemu_use_session if it's not set.
766
+ if @uri == UNSET_VALUE
767
+ @uri = _generate_uri(@qemu_use_session == UNSET_VALUE ? false : @qemu_use_session)
768
+ end
769
+
770
+ finalize_from_uri
771
+ finalize_proxy_command
772
+
697
773
  @storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
698
774
  @snapshot_pool_name = @storage_pool_name if @snapshot_pool_name == UNSET_VALUE
699
775
  @storage_pool_path = nil if @storage_pool_path == UNSET_VALUE
@@ -710,22 +786,6 @@ module VagrantPlugins
710
786
  @management_network_domain = nil if @management_network_domain == UNSET_VALUE
711
787
  @system_uri = 'qemu:///system' if @system_uri == UNSET_VALUE
712
788
 
713
- # If uri isn't set then let's build one from various sources.
714
- # Default to passing false for qemu_use_session if it's not set.
715
- if @uri == UNSET_VALUE
716
- @uri = _generate_uri(@qemu_use_session == UNSET_VALUE ? false : @qemu_use_session)
717
- end
718
-
719
- # Set qemu_use_session based on the URI if it wasn't set by the user
720
- if @qemu_use_session == UNSET_VALUE
721
- uri = _parse_uri(@uri)
722
- if (uri.scheme.start_with? "qemu") && (uri.path.include? "session")
723
- @qemu_use_session = true
724
- else
725
- @qemu_use_session = false
726
- end
727
- end
728
-
729
789
  # Domain specific settings.
730
790
  @title = '' if @title == UNSET_VALUE
731
791
  @description = '' if @description == UNSET_VALUE
@@ -749,6 +809,8 @@ module VagrantPlugins
749
809
  @shares = nil if @shares == UNSET_VALUE
750
810
  @features = ['acpi','apic','pae'] if @features == UNSET_VALUE
751
811
  @features_hyperv = [] if @features_hyperv == UNSET_VALUE
812
+ @clock_offset = 'utc' if @clock_offset == UNSET_VALUE
813
+ @clock_timers = [] if @clock_timers == UNSET_VALUE
752
814
  @numa_nodes = @numa_nodes == UNSET_VALUE ? nil : _generate_numa
753
815
  @loader = nil if @loader == UNSET_VALUE
754
816
  @nvram = nil if @nvram == UNSET_VALUE
@@ -757,9 +819,10 @@ module VagrantPlugins
757
819
  @machine_virtual_size = nil if @machine_virtual_size == UNSET_VALUE
758
820
  @disk_bus = 'virtio' if @disk_bus == UNSET_VALUE
759
821
  @disk_device = 'vda' if @disk_device == UNSET_VALUE
822
+ @disk_driver_opts = {} if @disk_driver_opts == UNSET_VALUE
760
823
  @nic_model_type = nil if @nic_model_type == UNSET_VALUE
761
824
  @nested = false if @nested == UNSET_VALUE
762
- @volume_cache = 'default' if @volume_cache == UNSET_VALUE
825
+ @volume_cache = nil if @volume_cache == UNSET_VALUE
763
826
  @kernel = nil if @kernel == UNSET_VALUE
764
827
  @cmd_line = '' if @cmd_line == UNSET_VALUE
765
828
  @initrd = '' if @initrd == UNSET_VALUE
@@ -781,6 +844,11 @@ module VagrantPlugins
781
844
  @tpm_model = 'tpm-tis' if @tpm_model == UNSET_VALUE
782
845
  @tpm_type = 'passthrough' if @tpm_type == UNSET_VALUE
783
846
  @tpm_path = nil if @tpm_path == UNSET_VALUE
847
+ @tpm_version = nil if @tpm_version == UNSET_VALUE
848
+ @memballoon_enabled = nil if @memballoon_enabled == UNSET_VALUE
849
+ @memballoon_model = 'virtio' if @memballoon_model == UNSET_VALUE
850
+ @memballoon_pci_bus = '0x00' if @memballoon_pci_bus == UNSET_VALUE
851
+ @memballoon_pci_slot = '0x0f' if @memballoon_pci_slot == UNSET_VALUE
784
852
  @nic_adapter_count = 8 if @nic_adapter_count == UNSET_VALUE
785
853
  @emulator_path = nil if @emulator_path == UNSET_VALUE
786
854
 
@@ -872,6 +940,14 @@ module VagrantPlugins
872
940
  end
873
941
  end
874
942
 
943
+ if !machine.provider_config.volume_cache.nil? and machine.provider_config.volume_cache != UNSET_VALUE
944
+ machine.ui.warn("Libvirt Provider: volume_cache is deprecated. Use disk_driver :cache => '#{machine.provider_config.volume_cache}' instead.")
945
+
946
+ if !machine.provider_config.disk_driver_opts.empty?
947
+ machine.ui.warn("Libvirt Provider: volume_cache has no effect when disk_driver is defined.")
948
+ end
949
+ end
950
+
875
951
  { 'Libvirt Provider' => errors }
876
952
  end
877
953
 
@@ -885,11 +961,96 @@ module VagrantPlugins
885
961
  c += other.cdroms
886
962
  result.cdroms = c
887
963
 
964
+ result.disk_driver_opts = disk_driver_opts.merge(other.disk_driver_opts)
965
+
966
+ c = clock_timers.dup
967
+ c += other.clock_timers
968
+ result.clock_timers = c
969
+
888
970
  c = qemu_env != UNSET_VALUE ? qemu_env.dup : {}
889
971
  c.merge!(other.qemu_env) if other.qemu_env != UNSET_VALUE
890
972
  result.qemu_env = c
891
973
  end
892
974
  end
975
+
976
+ private
977
+
978
+ def finalize_from_uri
979
+ # Parse uri to extract individual components
980
+ uri = _parse_uri(@uri)
981
+
982
+ # only set @connect_via_ssh if not explicitly to avoid overriding
983
+ # and allow an error to occur if the @uri and @connect_via_ssh disagree
984
+ @connect_via_ssh = uri.scheme.include? "ssh" if @connect_via_ssh == UNSET_VALUE
985
+
986
+ # Set qemu_use_session based on the URI if it wasn't set by the user
987
+ if @qemu_use_session == UNSET_VALUE
988
+ if (uri.scheme.start_with? "qemu") && (uri.path.include? "session")
989
+ @qemu_use_session = true
990
+ else
991
+ @qemu_use_session = false
992
+ end
993
+ end
994
+
995
+ # Extract host and username values from uri if provided, otherwise nil
996
+ @host = uri.host
997
+ @username = uri.user
998
+
999
+ finalize_id_ssh_key_file
1000
+ end
1001
+
1002
+ def resolve_ssh_key_file(key_file)
1003
+ # set ssh key for access to Libvirt host
1004
+ # if no slash, prepend $HOME/.ssh/
1005
+ key_file.prepend("#{ENV['HOME']}/.ssh/") if key_file && key_file !~ /\A\//
1006
+
1007
+ key_file
1008
+ end
1009
+
1010
+ def finalize_id_ssh_key_file
1011
+ # resolve based on the following roles
1012
+ # 1) if @connect_via_ssh is set to true, and id_ssh_key_file not current set,
1013
+ # set default if the file exists
1014
+ # 2) if supplied the key name, attempt to expand based on user home
1015
+ # 3) otherwise set to nil
1016
+
1017
+ if @connect_via_ssh == true && @id_ssh_key_file == UNSET_VALUE
1018
+ # set default if using ssh while allowing a user using nil to disable this
1019
+ id_ssh_key_file = resolve_ssh_key_file('id_rsa')
1020
+ id_ssh_key_file = nil if !File.file?(id_ssh_key_file)
1021
+ elsif @id_ssh_key_file != UNSET_VALUE
1022
+ id_ssh_key_file = resolve_ssh_key_file(@id_ssh_key_file)
1023
+ else
1024
+ id_ssh_key_file = nil
1025
+ end
1026
+
1027
+ @id_ssh_key_file = id_ssh_key_file
1028
+ end
1029
+
1030
+ def finalize_proxy_command
1031
+ if @connect_via_ssh
1032
+ if @proxy_command == UNSET_VALUE
1033
+ proxy_command = "ssh '#{@host}' "
1034
+ proxy_command << "-l '#{@username}' " if @username
1035
+ proxy_command << "-i '#{@id_ssh_key_file}' " if @id_ssh_key_file
1036
+ proxy_command << '-W %h:%p'
1037
+ else
1038
+ inputs = { host: @host }
1039
+ inputs[:username] = @username if @username
1040
+ inputs[:id_ssh_key_file] = @id_ssh_key_file if @id_ssh_key_file
1041
+
1042
+ proxy_command = @proxy_command
1043
+ # avoid needing to escape '%' symbols
1044
+ inputs.each do |key, value|
1045
+ proxy_command.gsub!("{#{key}}", value)
1046
+ end
1047
+ end
1048
+
1049
+ @proxy_command = proxy_command
1050
+ else
1051
+ @proxy_command = nil
1052
+ end
1053
+ end
893
1054
  end
894
1055
  end
895
1056
  end