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.
- checksums.yaml +4 -4
- data/README.md +421 -50
- data/lib/vagrant-libvirt/action.rb +7 -1
- data/lib/vagrant-libvirt/action/clean_machine_folder.rb +30 -0
- data/lib/vagrant-libvirt/action/create_domain.rb +56 -18
- data/lib/vagrant-libvirt/action/create_domain_volume.rb +57 -55
- data/lib/vagrant-libvirt/action/create_network_interfaces.rb +0 -3
- data/lib/vagrant-libvirt/action/create_networks.rb +11 -4
- data/lib/vagrant-libvirt/action/destroy_domain.rb +1 -1
- data/lib/vagrant-libvirt/action/forward_ports.rb +37 -38
- data/lib/vagrant-libvirt/action/halt_domain.rb +25 -9
- data/lib/vagrant-libvirt/action/handle_box_image.rb +163 -74
- data/lib/vagrant-libvirt/action/is_running.rb +1 -3
- data/lib/vagrant-libvirt/action/is_suspended.rb +4 -4
- data/lib/vagrant-libvirt/action/package_domain.rb +2 -1
- data/lib/vagrant-libvirt/action/set_boot_order.rb +6 -2
- data/lib/vagrant-libvirt/action/start_domain.rb +86 -29
- data/lib/vagrant-libvirt/action/wait_till_up.rb +8 -52
- data/lib/vagrant-libvirt/cap/{mount_p9.rb → mount_9p.rb} +2 -2
- data/lib/vagrant-libvirt/cap/mount_virtiofs.rb +37 -0
- data/lib/vagrant-libvirt/cap/{synced_folder.rb → synced_folder_9p.rb} +4 -5
- data/lib/vagrant-libvirt/cap/synced_folder_virtiofs.rb +109 -0
- data/lib/vagrant-libvirt/config.rb +236 -43
- data/lib/vagrant-libvirt/driver.rb +49 -32
- data/lib/vagrant-libvirt/errors.rb +24 -1
- data/lib/vagrant-libvirt/plugin.rb +14 -5
- data/lib/vagrant-libvirt/provider.rb +2 -9
- data/lib/vagrant-libvirt/templates/domain.xml.erb +35 -10
- data/lib/vagrant-libvirt/templates/private_network.xml.erb +1 -1
- data/lib/vagrant-libvirt/util/network_util.rb +21 -3
- data/lib/vagrant-libvirt/version +1 -1
- data/lib/vagrant-libvirt/version.rb +57 -9
- data/locales/en.yml +12 -0
- data/spec/spec_helper.rb +37 -3
- data/spec/support/binding_proc.rb +24 -0
- data/spec/support/libvirt_context.rb +2 -0
- data/spec/support/matchers/have_file_content.rb +63 -0
- data/spec/support/sharedcontext.rb +4 -0
- data/spec/unit/action/clean_machine_folder_spec.rb +58 -0
- data/spec/unit/action/create_domain_spec.rb +121 -36
- data/spec/unit/action/create_domain_spec/additional_disks_domain.xml +54 -0
- data/spec/unit/action/create_domain_spec/default_domain.xml +49 -0
- data/spec/unit/action/create_domain_spec/{default_storage_pool.xml → default_system_storage_pool.xml} +0 -0
- data/spec/unit/action/create_domain_spec/default_user_storage_pool.xml +17 -0
- data/spec/unit/action/create_domain_volume_spec.rb +102 -0
- data/spec/unit/action/create_domain_volume_spec/one_disk_in_storage.xml +21 -0
- data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_0.xml +21 -0
- data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_1.xml +21 -0
- data/spec/unit/action/create_domain_volume_spec/three_disks_in_storage_disk_2.xml +21 -0
- data/spec/unit/action/destroy_domain_spec.rb +1 -1
- data/spec/unit/action/forward_ports_spec.rb +202 -0
- data/spec/unit/action/halt_domain_spec.rb +90 -0
- data/spec/unit/action/handle_box_image_spec.rb +363 -0
- data/spec/unit/action/start_domain_spec.rb +183 -1
- data/spec/unit/action/start_domain_spec/clock_timer_rtc.xml +50 -0
- data/spec/unit/action/start_domain_spec/default.xml +2 -2
- data/spec/unit/action/start_domain_spec/default_added_tpm_path.xml +48 -0
- data/spec/unit/action/start_domain_spec/default_added_tpm_version.xml +48 -0
- data/spec/unit/action/wait_till_up_spec.rb +22 -21
- data/spec/unit/config_spec.rb +395 -127
- data/spec/unit/templates/domain_all_settings.xml +14 -3
- data/spec/unit/templates/domain_custom_cpu_model.xml +2 -1
- data/spec/unit/templates/domain_defaults.xml +2 -1
- data/spec/unit/templates/domain_spec.rb +100 -3
- data/spec/unit/templates/tpm/version_1.2.xml +54 -0
- data/spec/unit/templates/tpm/version_2.0.xml +53 -0
- 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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
29
|
-
|
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
|
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
|
-
#
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
37
|
-
env[:
|
38
|
-
|
39
|
-
|
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[:
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
112
|
+
end
|
113
|
+
|
114
|
+
@app.call(env)
|
115
|
+
end
|
84
116
|
|
85
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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] =
|
19
|
+
env[:result] = env[:machine].state.id == :shutoff
|
20
20
|
else
|
21
|
-
env[:result] =
|
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] =
|
30
|
+
env[:result] = [:shutoff, :paused].include?(env[:machine].state.id)
|
31
31
|
config.suspend_mode = 'managedsave'
|
32
32
|
else
|
33
|
-
env[:result] =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
185
|
-
if
|
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
|