vagrant-libvirt 0.3.0 → 0.4.0

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