vagrant-libvirt 0.3.0 → 0.5.2

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 (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