vagrant-libvirt 0.3.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +421 -50
  3. data/lib/vagrant-libvirt/action.rb +7 -1
  4. data/lib/vagrant-libvirt/action/clean_machine_folder.rb +30 -0
  5. data/lib/vagrant-libvirt/action/create_domain.rb +56 -18
  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 +37 -38
  11. data/lib/vagrant-libvirt/action/halt_domain.rb +25 -9
  12. data/lib/vagrant-libvirt/action/handle_box_image.rb +163 -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/package_domain.rb +2 -1
  16. data/lib/vagrant-libvirt/action/set_boot_order.rb +6 -2
  17. data/lib/vagrant-libvirt/action/start_domain.rb +86 -29
  18. data/lib/vagrant-libvirt/action/wait_till_up.rb +8 -52
  19. data/lib/vagrant-libvirt/cap/{mount_p9.rb → mount_9p.rb} +2 -2
  20. data/lib/vagrant-libvirt/cap/mount_virtiofs.rb +37 -0
  21. data/lib/vagrant-libvirt/cap/{synced_folder.rb → synced_folder_9p.rb} +4 -5
  22. data/lib/vagrant-libvirt/cap/synced_folder_virtiofs.rb +109 -0
  23. data/lib/vagrant-libvirt/config.rb +236 -43
  24. data/lib/vagrant-libvirt/driver.rb +49 -32
  25. data/lib/vagrant-libvirt/errors.rb +24 -1
  26. data/lib/vagrant-libvirt/plugin.rb +14 -5
  27. data/lib/vagrant-libvirt/provider.rb +2 -9
  28. data/lib/vagrant-libvirt/templates/domain.xml.erb +35 -10
  29. data/lib/vagrant-libvirt/templates/private_network.xml.erb +1 -1
  30. data/lib/vagrant-libvirt/util/network_util.rb +21 -3
  31. data/lib/vagrant-libvirt/version +1 -1
  32. data/lib/vagrant-libvirt/version.rb +57 -9
  33. data/locales/en.yml +12 -0
  34. data/spec/spec_helper.rb +37 -3
  35. data/spec/support/binding_proc.rb +24 -0
  36. data/spec/support/libvirt_context.rb +2 -0
  37. data/spec/support/matchers/have_file_content.rb +63 -0
  38. data/spec/support/sharedcontext.rb +4 -0
  39. data/spec/unit/action/clean_machine_folder_spec.rb +58 -0
  40. data/spec/unit/action/create_domain_spec.rb +121 -36
  41. data/spec/unit/action/create_domain_spec/additional_disks_domain.xml +54 -0
  42. data/spec/unit/action/create_domain_spec/default_domain.xml +49 -0
  43. data/spec/unit/action/create_domain_spec/{default_storage_pool.xml → default_system_storage_pool.xml} +0 -0
  44. data/spec/unit/action/create_domain_spec/default_user_storage_pool.xml +17 -0
  45. data/spec/unit/action/create_domain_volume_spec.rb +102 -0
  46. data/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml +21 -0
  47. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml +21 -0
  48. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml +21 -0
  49. data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml +21 -0
  50. data/spec/unit/action/destroy_domain_spec.rb +1 -1
  51. data/spec/unit/action/forward_ports_spec.rb +202 -0
  52. data/spec/unit/action/halt_domain_spec.rb +90 -0
  53. data/spec/unit/action/handle_box_image_spec.rb +363 -0
  54. data/spec/unit/action/start_domain_spec.rb +183 -1
  55. data/spec/unit/action/start_domain_spec/clock_timer_rtc.xml +50 -0
  56. data/spec/unit/action/start_domain_spec/default.xml +2 -2
  57. data/spec/unit/action/start_domain_spec/default_added_tpm_path.xml +48 -0
  58. data/spec/unit/action/start_domain_spec/default_added_tpm_version.xml +48 -0
  59. data/spec/unit/action/wait_till_up_spec.rb +22 -21
  60. data/spec/unit/config_spec.rb +395 -127
  61. data/spec/unit/templates/domain_all_settings.xml +14 -3
  62. data/spec/unit/templates/domain_custom_cpu_model.xml +2 -1
  63. data/spec/unit/templates/domain_defaults.xml +2 -1
  64. data/spec/unit/templates/domain_spec.rb +100 -3
  65. data/spec/unit/templates/tpm/version_1.2.xml +54 -0
  66. data/spec/unit/templates/tpm/version_2.0.xml +53 -0
  67. metadata +105 -19
@@ -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)
@@ -1,4 +1,5 @@
1
1
  require 'log4r'
2
+ require 'open3'
2
3
 
3
4
  module VagrantPlugins
4
5
  module ProviderLibvirt
@@ -16,33 +17,69 @@ module VagrantPlugins
16
17
  end
17
18
 
18
19
  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?
20
+ # Handle box formats converting between v1 => v2 and ensuring
21
+ # any obsolete settings are rejected.
24
22
 
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
23
+ disks = env[:machine].box.metadata.fetch('disks', [])
24
+ if disks.empty?
25
+ # Handle box v1 format
26
+
27
+ # Only qcow2 format is supported in v1, but other formats with backing
28
+ # store capability should be usable.
29
+ box_format = env[:machine].box.metadata['format']
30
+ HandleBoxImage.verify_box_format(box_format)
31
+
32
+ env[:box_volume_number] = 1
33
+ env[:box_volumes] = [{
34
+ :path => HandleBoxImage.get_box_image_path(env[:machine].box, 'box.img'),
35
+ :name => HandleBoxImage.get_volume_name(env[:machine].box, 'box'),
36
+ :virtual_size => HandleBoxImage.get_virtual_size(env),
37
+ :format => box_format,
38
+ }]
39
+ else
40
+ # Handle box v2 format
41
+ # {
42
+ # 'path': '<path-of-file-box>',
43
+ # 'name': '<name-to-use-in-storage>' # optional, will use index
44
+ # }
45
+ #
46
+ env[:box_volume_number] = disks.length()
47
+ target_volumes = Hash[]
48
+ env[:box_volumes] = Array.new(env[:box_volume_number]) { |i|
49
+ raise Errors::BoxFormatMissingAttribute, attribute: "disks[#{i}]['path']" if disks[i]['path'].nil?
50
+
51
+ image_path = HandleBoxImage.get_box_image_path(env[:machine].box, disks[i]['path'])
52
+ format, virtual_size = HandleBoxImage.get_box_disk_settings(image_path)
53
+ volume_name = HandleBoxImage.get_volume_name(
54
+ env[:machine].box,
55
+ disks[i].fetch('name', disks[i]['path'].sub(/#{File.extname(disks[i]['path'])}$/, '')),
56
+ )
57
+
58
+ # allowing name means needing to check that it doesn't cause a clash
59
+ existing = target_volumes[volume_name]
60
+ if !existing.nil?
61
+ raise Errors::BoxFormatDuplicateVolume, volume: volume_name, new_disk: "disks[#{i}]", orig_disk: "disks[#{existing}]"
62
+ end
63
+ target_volumes[volume_name] = i
64
+
65
+ {
66
+ :path => image_path,
67
+ :name => volume_name,
68
+ :virtual_size => virtual_size.to_i,
69
+ :format => HandleBoxImage.verify_box_format(format)
70
+ }
71
+ }
32
72
  end
33
73
 
34
74
  # Get config options
35
75
  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"
76
+ box_image_files = []
77
+ env[:box_volumes].each do |d|
78
+ box_image_files.push(d[:path])
79
+ end
44
80
 
45
81
  # Override box_virtual_size
82
+ box_virtual_size = env[:box_volumes][0][:virtual_size]
46
83
  if config.machine_virtual_size
47
84
  if config.machine_virtual_size < box_virtual_size
48
85
  # Warn that a virtual size less than the box metadata size
@@ -57,77 +94,129 @@ module VagrantPlugins
57
94
  end
58
95
  end
59
96
  # save for use by later actions
60
- env[:box_virtual_size] = box_virtual_size
97
+ env[:box_volumes][0][:virtual_size] = box_virtual_size
61
98
 
62
99
  # while inside the synchronize block take care not to call the next
63
100
  # action in the chain, as must exit this block first to prevent
64
101
  # locking all subsequent actions as well.
65
102
  @@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
103
+ env[:box_volumes].each_index do |i|
104
+ # Don't continue if image already exists in storage pool.
105
+ box_volume = env[:machine].provider.driver.connection.volumes.all(
106
+ name: env[:box_volumes][i][:name]
107
+ ).first
108
+ next if box_volume && box_volume.id
109
+
110
+ send_box_image(env, config, box_image_files[i], env[:box_volumes][i])
79
111
  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)
112
+ end
113
+
114
+ @app.call(env)
115
+ end
84
116
 
85
- @storage_volume_uid = storage_uid env
86
- @storage_volume_gid = storage_gid env
117
+ protected
87
118
 
119
+ def self.get_volume_name(box, name)
120
+ vol_name = box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
121
+ vol_name << "_vagrant_box_image_#{
88
122
  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
123
+ box.version.to_s
124
+ rescue
125
+ ''
101
126
  end
127
+ }_#{name.dup.gsub('/', '-SLASH-')}.img"
128
+ end
102
129
 
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
130
+ def self.get_virtual_size(env)
131
+ # Virtual size has to be set for allocating space in storage pool.
132
+ box_virtual_size = env[:machine].box.metadata['virtual_size']
133
+ raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
134
+ return box_virtual_size
135
+ end
111
136
 
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
137
+ def self.get_box_image_path(box, box_name)
138
+ return box.directory.join(box_name).to_s
139
+ end
140
+
141
+ def self.verify_box_format(box_format, disk_index=nil)
142
+ if box_format.nil?
143
+ raise Errors::NoBoxFormatSet
144
+ elsif box_format != 'qcow2'
145
+ if disk_index.nil?
146
+ raise Errors::WrongBoxFormatSet
147
+ else
148
+ raise Errors::WrongDiskFormatSet,
149
+ disk_index: disk_index
124
150
  end
125
151
  end
152
+ return box_format
153
+ end
126
154
 
127
- @app.call(env)
155
+ def self.get_box_disk_settings(image_path)
156
+ stdout, stderr, status = Open3.capture3('qemu-img', 'info', image_path)
157
+ if !status.success?
158
+ raise Errors::BadBoxImage, image: image_path, out: stdout, err: stderr
159
+ end
160
+
161
+ image_info_lines = stdout.split("\n")
162
+ format = image_info_lines.find { |l| l.start_with?('file format:') }.split(' ')[2]
163
+ virtual_size = image_info_lines.find { |l| l.start_with?('virtual size:') }.split(' ')[2]
164
+
165
+ return format, virtual_size
128
166
  end
129
167
 
130
- protected
168
+ def send_box_image(env, config, box_image_file, box_volume)
169
+ # Box is not available as a storage pool volume. Create and upload
170
+ # it as a copy of local box image.
171
+ env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))
172
+
173
+ # Create new volume in storage pool
174
+ unless File.exist?(box_image_file)
175
+ raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
176
+ end
177
+ box_image_size = File.size(box_image_file) # B
178
+ message = "Creating volume #{box_volume[:name]}"
179
+ message << " in storage pool #{config.storage_pool_name}."
180
+ @logger.info(message)
181
+
182
+ begin
183
+ fog_volume = env[:machine].provider.driver.connection.volumes.create(
184
+ name: box_volume[:name],
185
+ allocation: "#{box_image_size / 1024 / 1024}M",
186
+ capacity: "#{box_volume[:virtual_size]}G",
187
+ format_type: box_volume[:format],
188
+ owner: storage_uid(env),
189
+ group: storage_gid(env),
190
+ pool_name: config.storage_pool_name
191
+ )
192
+ rescue Fog::Errors::Error => e
193
+ raise Errors::FogCreateVolumeError,
194
+ error_message: e.message
195
+ end
196
+
197
+ # Upload box image to storage pool
198
+ ret = upload_image(box_image_file, config.storage_pool_name,
199
+ box_volume[:name], env) do |progress|
200
+ rewriting(env[:ui]) do |ui|
201
+ ui.clear_line
202
+ ui.report_progress(progress, box_image_size, false)
203
+ end
204
+ end
205
+
206
+ # Clear the line one last time since the progress meter doesn't
207
+ # disappear immediately.
208
+ rewriting(env[:ui]) {|ui| ui.clear_line}
209
+
210
+ # If upload failed or was interrupted, remove created volume from
211
+ # storage pool.
212
+ if env[:interrupted] || !ret
213
+ begin
214
+ fog_volume.destroy
215
+ rescue
216
+ nil
217
+ end
218
+ end
219
+ end
131
220
 
132
221
  # Fog Libvirt currently doesn't support uploading images to storage
133
222
  # pool volumes. Use ruby-libvirt client instead.
@@ -9,9 +9,7 @@ module VagrantPlugins
9
9
  end
10
10
 
11
11
  def call(env)
12
- domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
13
- raise Errors::NoDomainError if domain.nil?
14
- env[:result] = domain.state.to_s == 'running'
12
+ env[:result] = env[:machine].state.id == :running
15
13
 
16
14
  @app.call(env)
17
15
  end
@@ -16,9 +16,9 @@ module VagrantPlugins
16
16
  libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(env[:machine].id)
17
17
  if config.suspend_mode == 'managedsave'
18
18
  if libvirt_domain.has_managed_save?
19
- env[:result] = libvirt_domain.has_managed_save?
19
+ env[:result] = env[:machine].state.id == :shutoff
20
20
  else
21
- env[:result] = domain.state.to_s == 'paused'
21
+ env[:result] = env[:machine].state.id == :paused
22
22
  if env[:result]
23
23
  env[:ui].warn('One time switching to pause suspend mode, found a paused VM.')
24
24
  config.suspend_mode = 'pause'
@@ -27,10 +27,10 @@ module VagrantPlugins
27
27
  else
28
28
  if libvirt_domain.has_managed_save?
29
29
  env[:ui].warn('One time switching to managedsave suspend mode, state found.')
30
- env[:result] = true
30
+ env[:result] = [:shutoff, :paused].include?(env[:machine].state.id)
31
31
  config.suspend_mode = 'managedsave'
32
32
  else
33
- env[:result] = domain.state.to_s == 'paused'
33
+ env[:result] = env[:machine].state.id == :paused
34
34
  end
35
35
  end
36
36
 
@@ -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'
@@ -86,9 +86,13 @@ module VagrantPlugins
86
86
 
87
87
  def search_network(nets, xml)
88
88
  str = '/domain/devices/interface'
89
- str += "[(@type='network' or @type='udp' or @type='bridge')"
89
+ str += "[(@type='network' or @type='udp' or @type='bridge' or @type='direct')"
90
90
  unless nets.empty?
91
- str += " and source[@network='#{nets.first['network']}']"
91
+ net = nets.first
92
+ network = net['network']
93
+ dev = net['dev']
94
+ str += " and source[@network='#{network}']" if network
95
+ str += " and source[@dev='#{dev}']" if dev
92
96
  end
93
97
  str += ']'
94
98
  @logger.debug(str)
@@ -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