Reduce nested rescues in start domain (#1573)

Remove the unnecessary nesting of begin/rescue entries in start domain
which partially existed due to vagrant-libvirt destroying domain
definitions on error, even for machines that had previously been
created.

Since this is no longer a problem as existing domains will be halted
rather than removed when an exception occurs, it is perfectly fine to
let the exception percolate upwards and be handled by the warden, which
is what the current code is doing, but at the expense of catching and
re-raising multiple times.
This commit is contained in:
Darragh Bailey 2022-09-03 16:28:56 +01:00 committed by GitHub
parent 606b86df67
commit 316ca8e1d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 430 additions and 425 deletions

View File

@ -20,439 +20,438 @@ module VagrantPlugins
def call(env)
domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)
raise Errors::NoDomainError if domain.nil?
config = env[:machine].provider_config
begin
# update domain settings on change.
# update domain settings on change.
libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(env[:machine].id)
libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(env[:machine].id)
# Libvirt API doesn't support modifying memory on NUMA enabled CPUs
# http://libvirt.org/git/?p=libvirt.git;a=commit;h=d174394105cf00ed266bf729ddf461c21637c736
if config.numa_nodes == nil
if config.memory.to_i * 1024 != libvirt_domain.max_memory
libvirt_domain.max_memory = config.memory.to_i * 1024
libvirt_domain.memory = libvirt_domain.max_memory
end
end
# Libvirt API doesn't support modifying memory on NUMA enabled CPUs
# http://libvirt.org/git/?p=libvirt.git;a=commit;h=d174394105cf00ed266bf729ddf461c21637c736
if config.numa_nodes == nil
if config.memory.to_i * 1024 != libvirt_domain.max_memory
libvirt_domain.max_memory = config.memory.to_i * 1024
libvirt_domain.memory = libvirt_domain.max_memory
# XML definition manipulation
descr = libvirt_domain.xml_desc(1)
xml_descr = REXML::Document.new descr
descr_changed = false
# For outputting XML for comparison
formatter = REXML::Formatters::Pretty.new
# additional disk bus
config.disks.each do |disk|
device = disk[:device]
bus = disk[:bus]
REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="' + device + '"]') do |disk_target|
next unless disk_target.attributes['bus'] != bus
@logger.debug "disk #{device} bus updated from '#{disk_target.attributes['bus']}' to '#{bus}'"
descr_changed = true
disk_target.attributes['bus'] = bus
disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
end
end
# disk_bus
REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="vda"]') do |disk_target|
next unless disk_target.attributes['bus'] != config.disk_bus
@logger.debug "domain disk bus updated from '#{disk_target.attributes['bus']}' to '#{config.disk_bus}'"
descr_changed = true
disk_target.attributes['bus'] = config.disk_bus
disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
end
# Interface type
unless config.nic_model_type.nil?
REXML::XPath.each(xml_descr, '/domain/devices/interface/model') do |iface_model|
if iface_model.attributes['type'] != config.nic_model_type
@logger.debug "network type updated from '#{iface_model.attributes['type']}' to '#{config.nic_model_type}'"
descr_changed = true
iface_model.attributes['type'] = config.nic_model_type
end
end
end
# vCpu count
vcpus_count = libvirt_domain.num_vcpus(0)
if config.cpus.to_i != vcpus_count
@logger.debug "cpu count updated from '#{vcpus_count}' to '#{config.cpus}'"
descr_changed = true
REXML::XPath.first(xml_descr, '/domain/vcpu').text = config.cpus
end
# cpu_mode
cpu = REXML::XPath.first(xml_descr, '/domain/cpu')
if cpu.nil?
@logger.debug "cpu_mode updated from not set to '#{config.cpu_mode}'"
descr_changed = true
cpu = REXML::Element.new('cpu', REXML::XPath.first(xml_descr, '/domain'))
cpu.attributes['mode'] = config.cpu_mode
else
if cpu.attributes['mode'] != config.cpu_mode
@logger.debug "cpu_mode updated from '#{cpu.attributes['mode']}' to '#{config.cpu_mode}'"
descr_changed = true
cpu.attributes['mode'] = config.cpu_mode
end
end
if config.cpu_mode != 'host-passthrough'
cpu_model = REXML::XPath.first(xml_descr, '/domain/cpu/model')
if cpu_model.nil?
if config.cpu_model.strip != ''
@logger.debug "cpu_model updated from not set to '#{config.cpu_model}'"
descr_changed = true
cpu_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/cpu'))
cpu_model.attributes['fallback'] = 'allow'
cpu_model.text = config.cpu_model
end
else
if (cpu_model.text or '').strip != config.cpu_model.strip
@logger.debug "cpu_model text updated from #{cpu_model.text} to '#{config.cpu_model}'"
descr_changed = true
cpu_model.text = config.cpu_model
end
if cpu_model.attributes['fallback'] != config.cpu_fallback
@logger.debug "cpu_model fallback attribute updated from #{cpu_model.attributes['fallback']} to '#{config.cpu_fallback}'"
descr_changed = true
cpu_model.attributes['fallback'] = config.cpu_fallback
end
end
vmx_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="vmx"]')
svm_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="svm"]')
if config.nested
if vmx_feature.nil?
@logger.debug "nested mode enabled from unset by setting cpu vmx feature"
descr_changed = true
vmx_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
vmx_feature.attributes['policy'] = 'optional'
vmx_feature.attributes['name'] = 'vmx'
end
if svm_feature.nil?
@logger.debug "nested mode enabled from unset by setting cpu svm feature"
descr_changed = true
svm_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
svm_feature.attributes['policy'] = 'optional'
svm_feature.attributes['name'] = 'svm'
end
else
unless vmx_feature.nil?
@logger.debug "nested mode disabled for cpu by removing vmx feature"
descr_changed = true
cpu.delete_element(vmx_feature)
end
unless svm_feature.nil?
@logger.debug "nested mode disabled for cpu by removing svm feature"
descr_changed = true
cpu.delete_element(svm_feature)
end
end
elsif config.numa_nodes == nil
unless cpu.elements.to_a.empty?
@logger.debug "switching cpu_mode to host-passthrough and removing emulated cpu features"
descr_changed = true
cpu.elements.each do |elem|
cpu.delete_element(elem)
end
end
end
# Clock
clock = REXML::XPath.first(xml_descr, '/domain/clock')
if clock.attributes['offset'] != config.clock_offset
@logger.debug "clock offset changed"
descr_changed = true
clock.attributes['offset'] = config.clock_offset
end
# clock timers - because timers can be added/removed, just rebuild and then compare
if !config.clock_timers.empty? || clock.has_elements?
oldclock = String.new
formatter.write(REXML::XPath.first(xml_descr, '/domain/clock'), oldclock)
clock.delete_element('//timer')
config.clock_timers.each do |clock_timer|
timer = REXML::Element.new('timer', clock)
clock_timer.each do |attr, value|
timer.attributes[attr.to_s] = value
end
end
newclock = String.new
formatter.write(clock, newclock)
unless newclock.eql? oldclock
@logger.debug "clock timers config changed"
descr_changed = true
end
end
# Graphics
graphics = REXML::XPath.first(xml_descr, '/domain/devices/graphics')
if config.graphics_type != 'none'
if graphics.nil?
descr_changed = true
graphics = REXML::Element.new('graphics', REXML::XPath.first(xml_descr, '/domain/devices'))
end
if graphics.attributes['type'] != config.graphics_type
descr_changed = true
graphics.attributes['type'] = config.graphics_type
end
if graphics.attributes['listen'] != config.graphics_ip
descr_changed = true
graphics.attributes['listen'] = config.graphics_ip
graphics.delete_element('//listen')
end
if graphics.attributes['autoport'] != config.graphics_autoport
descr_changed = true
graphics.attributes['autoport'] = config.graphics_autoport
if config.graphics_autoport == 'no'
graphics.attributes.delete('autoport')
graphics.attributes['port'] = config.graphics_port
end
end
if graphics.attributes['keymap'] != config.keymap
descr_changed = true
graphics.attributes['keymap'] = config.keymap
end
if graphics.attributes['passwd'] != config.graphics_passwd
descr_changed = true
if config.graphics_passwd.nil?
graphics.attributes.delete 'passwd'
else
graphics.attributes['passwd'] = config.graphics_passwd
end
end
graphics_gl = REXML::XPath.first(xml_descr, '/domain/devices/graphics/gl')
if graphics_gl.nil?
if config.graphics_gl
graphics_gl = REXML::Element.new('gl', REXML::XPath.first(xml_descr, '/domain/devices/graphics'))
graphics_gl.attributes['enable'] = 'yes'
descr_changed = true
end
else
if config.graphics_gl
if graphics_gl.attributes['enable'] != 'yes'
graphics_gl.attributes['enable'] = 'yes'
descr_changed = true
end
else
graphics_gl.parent.delete_element(graphics_gl)
descr_changed = true
end
end
else
# graphics_type = none, remove entire element
graphics.parent.delete_element(graphics) unless graphics.nil?
end
# TPM
if [config.tpm_path, config.tpm_version].any?
if config.tpm_path
raise Errors::UpdateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/'
end
# just build the tpm element every time
# check at the end if it is different
oldtpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm')
REXML::XPath.first(xml_descr, '/domain/devices').delete_element("tpm")
newtpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices'))
newtpm.attributes['model'] = config.tpm_model
backend = newtpm.add_element('backend')
backend.attributes['type'] = config.tpm_type
case config.tpm_type
when 'emulator'
backend.attributes['version'] = config.tpm_version
when 'passthrough'
backend.add_element('device').attributes['path'] = config.tpm_path
end
unless "'#{newtpm}'".eql? "'#{oldtpm}'"
@logger.debug "tpm config changed"
descr_changed = true
end
end
# Video device
video = REXML::XPath.first(xml_descr, '/domain/devices/video')
if !video.nil? && (config.graphics_type == 'none')
# graphics_type = none, video devices are removed since there is no possible output
@logger.debug "deleting video elements as config.graphics_type is none"
descr_changed = true
video.parent.delete_element(video)
else
video_model = REXML::XPath.first(xml_descr, '/domain/devices/video/model')
if video_model.nil?
@logger.debug "video updated from not set to type '#{config.video_type}' and vram '#{config.video_vram}'"
descr_changed = true
video_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/devices/video'))
video_model.attributes['type'] = config.video_type
video_model.attributes['vram'] = config.video_vram
else
if video_model.attributes['type'] != config.video_type || video_model.attributes['vram'] != config.video_vram.to_s
@logger.debug "video type updated from '#{video_model.attributes['type']}' to '#{config.video_type}'"
@logger.debug "video vram updated from '#{video_model.attributes['vram']}' to '#{config.video_vram}'"
descr_changed = true
video_model.attributes['type'] = config.video_type
video_model.attributes['vram'] = config.video_vram
end
end
video_accel = REXML::XPath.first(xml_descr, '/domain/devices/video/model/acceleration')
if video_accel.nil?
if config.video_accel3d
video_accel = REXML::Element.new('acceleration', REXML::XPath.first(xml_descr, '/domain/devices/video/model'))
video_accel.attributes['accel3d'] = 'yes'
descr_changed = true
end
else
if config.video_accel3d
if video_accel.attributes['accel3d'] != 'yes'
video_accel.attributes['accel3d'] = 'yes'
descr_changed = true
end
else
video_accel.parent.delete_element(video_accel)
descr_changed = true
end
end
end
# Sound device
if config.sound_type
sound = REXML::XPath.first(xml_descr,'/domain/devices/sound/model')
end
# dtb
if config.dtb
dtb = REXML::XPath.first(xml_descr, '/domain/os/dtb')
if dtb.nil?
@logger.debug "dtb updated from not set to '#{config.dtb}'"
descr_changed = true
dtb = REXML::Element.new('dtb', REXML::XPath.first(xml_descr, '/domain/os'))
dtb.text = config.dtb
else
if (dtb.text or '') != config.dtb
@logger.debug "dtb updated from '#{dtb.text}' to '#{config.dtb}'"
descr_changed = true
dtb.text = config.dtb
end
end
end
# kernel and initrd
if config.kernel
kernel = REXML::XPath.first(xml_descr, '/domain/os/kernel')
if kernel.nil?
@logger.debug "kernel updated from not set to '#{config.kernel}'"
descr_changed = true
kernel = REXML::Element.new('kernel', REXML::XPath.first(xml_descr, '/domain/os'))
kernel.text = config.kernel
else
if (kernel.text or '').strip != config.kernel
@logger.debug "kernel updated from '#{kernel.text}' to '#{config.kernel}'"
descr_changed = true
kernel.text = config.kernel
end
end
end
if config.initrd
initrd = REXML::XPath.first(xml_descr, '/domain/os/initrd')
if initrd.nil?
if config.initrd.strip != ''
@logger.debug "initrd updated from not set to '#{config.initrd}'"
descr_changed = true
initrd = REXML::Element.new('initrd', REXML::XPath.first(xml_descr, '/domain/os'))
initrd.text = config.initrd
end
else
if (initrd.text or '').strip != config.initrd
@logger.debug "initrd updated from '#{initrd.text}' to '#{config.initrd}'"
descr_changed = true
initrd.text = config.initrd
end
end
end
loader = REXML::XPath.first(xml_descr, '/domain/os/loader')
if config.loader
if loader.nil?
descr_changed = true
loader = REXML::Element.new('loader')
REXML::XPath.first(xml_descr, '/domain/os').insert_after('//type', loader)
loader.text = config.loader
else
if (loader.text or '').strip != config.loader
descr_changed = true
loader.text = config.loader
end
end
loader.attributes['type'] = config.nvram ? 'pflash' : 'rom'
elsif !loader.nil?
descr_changed = true
loader.parent.delete_element(loader)
end
nvram = REXML::XPath.first(xml_descr, '/domain/os/nvram')
if config.nvram
if nvram.nil?
descr_changed = true
nvram = REXML::Element.new('nvram')
REXML::XPath.first(xml_descr, '/domain/os').insert_after(loader, nvram)
nvram.text = config.nvram
else
if (nvram.text or '').strip != config.nvram
descr_changed = true
nvram.text = config.nvram
end
end
elsif !nvram.nil?
descr_changed = true
nvram.parent.delete_element(nvram)
end
# Apply
if descr_changed
env[:ui].info(I18n.t('vagrant_libvirt.updating_domain'))
new_xml = String.new
xml_descr.write(new_xml)
begin
# XML definition manipulation
descr = libvirt_domain.xml_desc(1)
xml_descr = REXML::Document.new descr
descr_changed = false
# For outputting XML for comparison
formatter = REXML::Formatters::Pretty.new
# additional disk bus
config.disks.each do |disk|
device = disk[:device]
bus = disk[:bus]
REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="' + device + '"]') do |disk_target|
next unless disk_target.attributes['bus'] != bus
@logger.debug "disk #{device} bus updated from '#{disk_target.attributes['bus']}' to '#{bus}'"
descr_changed = true
disk_target.attributes['bus'] = bus
disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
end
end
# disk_bus
REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="vda"]') do |disk_target|
next unless disk_target.attributes['bus'] != config.disk_bus
@logger.debug "domain disk bus updated from '#{disk_target.attributes['bus']}' to '#{config.disk_bus}'"
descr_changed = true
disk_target.attributes['bus'] = config.disk_bus
disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
end
# Interface type
unless config.nic_model_type.nil?
REXML::XPath.each(xml_descr, '/domain/devices/interface/model') do |iface_model|
if iface_model.attributes['type'] != config.nic_model_type
@logger.debug "network type updated from '#{iface_model.attributes['type']}' to '#{config.nic_model_type}'"
descr_changed = true
iface_model.attributes['type'] = config.nic_model_type
end
end
end
# vCpu count
vcpus_count = libvirt_domain.num_vcpus(0)
if config.cpus.to_i != vcpus_count
@logger.debug "cpu count updated from '#{vcpus_count}' to '#{config.cpus}'"
descr_changed = true
REXML::XPath.first(xml_descr, '/domain/vcpu').text = config.cpus
end
# cpu_mode
cpu = REXML::XPath.first(xml_descr, '/domain/cpu')
if cpu.nil?
@logger.debug "cpu_mode updated from not set to '#{config.cpu_mode}'"
descr_changed = true
cpu = REXML::Element.new('cpu', REXML::XPath.first(xml_descr, '/domain'))
cpu.attributes['mode'] = config.cpu_mode
else
if cpu.attributes['mode'] != config.cpu_mode
@logger.debug "cpu_mode updated from '#{cpu.attributes['mode']}' to '#{config.cpu_mode}'"
descr_changed = true
cpu.attributes['mode'] = config.cpu_mode
end
end
if config.cpu_mode != 'host-passthrough'
cpu_model = REXML::XPath.first(xml_descr, '/domain/cpu/model')
if cpu_model.nil?
if config.cpu_model.strip != ''
@logger.debug "cpu_model updated from not set to '#{config.cpu_model}'"
descr_changed = true
cpu_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/cpu'))
cpu_model.attributes['fallback'] = 'allow'
cpu_model.text = config.cpu_model
end
else
if (cpu_model.text or '').strip != config.cpu_model.strip
@logger.debug "cpu_model text updated from #{cpu_model.text} to '#{config.cpu_model}'"
descr_changed = true
cpu_model.text = config.cpu_model
end
if cpu_model.attributes['fallback'] != config.cpu_fallback
@logger.debug "cpu_model fallback attribute updated from #{cpu_model.attributes['fallback']} to '#{config.cpu_fallback}'"
descr_changed = true
cpu_model.attributes['fallback'] = config.cpu_fallback
end
end
vmx_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="vmx"]')
svm_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="svm"]')
if config.nested
if vmx_feature.nil?
@logger.debug "nested mode enabled from unset by setting cpu vmx feature"
descr_changed = true
vmx_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
vmx_feature.attributes['policy'] = 'optional'
vmx_feature.attributes['name'] = 'vmx'
end
if svm_feature.nil?
@logger.debug "nested mode enabled from unset by setting cpu svm feature"
descr_changed = true
svm_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
svm_feature.attributes['policy'] = 'optional'
svm_feature.attributes['name'] = 'svm'
end
else
unless vmx_feature.nil?
@logger.debug "nested mode disabled for cpu by removing vmx feature"
descr_changed = true
cpu.delete_element(vmx_feature)
end
unless svm_feature.nil?
@logger.debug "nested mode disabled for cpu by removing svm feature"
descr_changed = true
cpu.delete_element(svm_feature)
end
end
elsif config.numa_nodes == nil
unless cpu.elements.to_a.empty?
@logger.debug "switching cpu_mode to host-passthrough and removing emulated cpu features"
descr_changed = true
cpu.elements.each do |elem|
cpu.delete_element(elem)
end
end
end
# Clock
clock = REXML::XPath.first(xml_descr, '/domain/clock')
if clock.attributes['offset'] != config.clock_offset
@logger.debug "clock offset changed"
descr_changed = true
clock.attributes['offset'] = config.clock_offset
end
# clock timers - because timers can be added/removed, just rebuild and then compare
if !config.clock_timers.empty? || clock.has_elements?
oldclock = String.new
formatter.write(REXML::XPath.first(xml_descr, '/domain/clock'), oldclock)
clock.delete_element('//timer')
config.clock_timers.each do |clock_timer|
timer = REXML::Element.new('timer', clock)
clock_timer.each do |attr, value|
timer.attributes[attr.to_s] = value
end
end
newclock = String.new
formatter.write(clock, newclock)
unless newclock.eql? oldclock
@logger.debug "clock timers config changed"
descr_changed = true
end
end
# Graphics
graphics = REXML::XPath.first(xml_descr, '/domain/devices/graphics')
if config.graphics_type != 'none'
if graphics.nil?
descr_changed = true
graphics = REXML::Element.new('graphics', REXML::XPath.first(xml_descr, '/domain/devices'))
end
if graphics.attributes['type'] != config.graphics_type
descr_changed = true
graphics.attributes['type'] = config.graphics_type
end
if graphics.attributes['listen'] != config.graphics_ip
descr_changed = true
graphics.attributes['listen'] = config.graphics_ip
graphics.delete_element('//listen')
end
if graphics.attributes['autoport'] != config.graphics_autoport
descr_changed = true
graphics.attributes['autoport'] = config.graphics_autoport
if config.graphics_autoport == 'no'
graphics.attributes.delete('autoport')
graphics.attributes['port'] = config.graphics_port
end
end
if graphics.attributes['keymap'] != config.keymap
descr_changed = true
graphics.attributes['keymap'] = config.keymap
end
if graphics.attributes['passwd'] != config.graphics_passwd
descr_changed = true
if config.graphics_passwd.nil?
graphics.attributes.delete 'passwd'
else
graphics.attributes['passwd'] = config.graphics_passwd
end
end
graphics_gl = REXML::XPath.first(xml_descr, '/domain/devices/graphics/gl')
if graphics_gl.nil?
if config.graphics_gl
graphics_gl = REXML::Element.new('gl', REXML::XPath.first(xml_descr, '/domain/devices/graphics'))
graphics_gl.attributes['enable'] = 'yes'
descr_changed = true
end
else
if config.graphics_gl
if graphics_gl.attributes['enable'] != 'yes'
graphics_gl.attributes['enable'] = 'yes'
descr_changed = true
end
else
graphics_gl.parent.delete_element(graphics_gl)
descr_changed = true
end
end
else
# graphics_type = none, remove entire element
graphics.parent.delete_element(graphics) unless graphics.nil?
end
# TPM
if [config.tpm_path, config.tpm_version].any?
if config.tpm_path
raise Errors::UpdateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/'
end
# just build the tpm element every time
# check at the end if it is different
oldtpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm')
REXML::XPath.first(xml_descr, '/domain/devices').delete_element("tpm")
newtpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices'))
newtpm.attributes['model'] = config.tpm_model
backend = newtpm.add_element('backend')
backend.attributes['type'] = config.tpm_type
case config.tpm_type
when 'emulator'
backend.attributes['version'] = config.tpm_version
when 'passthrough'
backend.add_element('device').attributes['path'] = config.tpm_path
end
unless "'#{newtpm}'".eql? "'#{oldtpm}'"
@logger.debug "tpm config changed"
descr_changed = true
end
end
# Video device
video = REXML::XPath.first(xml_descr, '/domain/devices/video')
if !video.nil? && (config.graphics_type == 'none')
# graphics_type = none, video devices are removed since there is no possible output
@logger.debug "deleting video elements as config.graphics_type is none"
descr_changed = true
video.parent.delete_element(video)
else
video_model = REXML::XPath.first(xml_descr, '/domain/devices/video/model')
if video_model.nil?
@logger.debug "video updated from not set to type '#{config.video_type}' and vram '#{config.video_vram}'"
descr_changed = true
video_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/devices/video'))
video_model.attributes['type'] = config.video_type
video_model.attributes['vram'] = config.video_vram
else
if video_model.attributes['type'] != config.video_type || video_model.attributes['vram'] != config.video_vram.to_s
@logger.debug "video type updated from '#{video_model.attributes['type']}' to '#{config.video_type}'"
@logger.debug "video vram updated from '#{video_model.attributes['vram']}' to '#{config.video_vram}'"
descr_changed = true
video_model.attributes['type'] = config.video_type
video_model.attributes['vram'] = config.video_vram
end
end
video_accel = REXML::XPath.first(xml_descr, '/domain/devices/video/model/acceleration')
if video_accel.nil?
if config.video_accel3d
video_accel = REXML::Element.new('acceleration', REXML::XPath.first(xml_descr, '/domain/devices/video/model'))
video_accel.attributes['accel3d'] = 'yes'
descr_changed = true
end
else
if config.video_accel3d
if video_accel.attributes['accel3d'] != 'yes'
video_accel.attributes['accel3d'] = 'yes'
descr_changed = true
end
else
video_accel.parent.delete_element(video_accel)
descr_changed = true
end
end
end
# Sound device
if config.sound_type
sound = REXML::XPath.first(xml_descr,'/domain/devices/sound/model')
end
# dtb
if config.dtb
dtb = REXML::XPath.first(xml_descr, '/domain/os/dtb')
if dtb.nil?
@logger.debug "dtb updated from not set to '#{config.dtb}'"
descr_changed = true
dtb = REXML::Element.new('dtb', REXML::XPath.first(xml_descr, '/domain/os'))
dtb.text = config.dtb
else
if (dtb.text or '') != config.dtb
@logger.debug "dtb updated from '#{dtb.text}' to '#{config.dtb}'"
descr_changed = true
dtb.text = config.dtb
end
end
end
# kernel and initrd
if config.kernel
kernel = REXML::XPath.first(xml_descr, '/domain/os/kernel')
if kernel.nil?
@logger.debug "kernel updated from not set to '#{config.kernel}'"
descr_changed = true
kernel = REXML::Element.new('kernel', REXML::XPath.first(xml_descr, '/domain/os'))
kernel.text = config.kernel
else
if (kernel.text or '').strip != config.kernel
@logger.debug "kernel updated from '#{kernel.text}' to '#{config.kernel}'"
descr_changed = true
kernel.text = config.kernel
end
end
end
if config.initrd
initrd = REXML::XPath.first(xml_descr, '/domain/os/initrd')
if initrd.nil?
if config.initrd.strip != ''
@logger.debug "initrd updated from not set to '#{config.initrd}'"
descr_changed = true
initrd = REXML::Element.new('initrd', REXML::XPath.first(xml_descr, '/domain/os'))
initrd.text = config.initrd
end
else
if (initrd.text or '').strip != config.initrd
@logger.debug "initrd updated from '#{initrd.text}' to '#{config.initrd}'"
descr_changed = true
initrd.text = config.initrd
end
end
end
loader = REXML::XPath.first(xml_descr, '/domain/os/loader')
if config.loader
if loader.nil?
descr_changed = true
loader = REXML::Element.new('loader')
REXML::XPath.first(xml_descr, '/domain/os').insert_after('//type', loader)
loader.text = config.loader
else
if (loader.text or '').strip != config.loader
descr_changed = true
loader.text = config.loader
end
end
loader.attributes['type'] = config.nvram ? 'pflash' : 'rom'
elsif !loader.nil?
descr_changed = true
loader.parent.delete_element(loader)
end
nvram = REXML::XPath.first(xml_descr, '/domain/os/nvram')
if config.nvram
if nvram.nil?
descr_changed = true
nvram = REXML::Element.new('nvram')
REXML::XPath.first(xml_descr, '/domain/os').insert_after(loader, nvram)
nvram.text = config.nvram
else
if (nvram.text or '').strip != config.nvram
descr_changed = true
nvram.text = config.nvram
end
end
elsif !nvram.nil?
descr_changed = true
nvram.parent.delete_element(nvram)
end
# Apply
if descr_changed
env[:ui].info(I18n.t('vagrant_libvirt.updating_domain'))
new_xml = String.new
xml_descr.write(new_xml)
begin
# providing XML for the same name and UUID will update the existing domain
libvirt_domain = env[:machine].provider.driver.connection.define_domain(new_xml)
rescue ::Libvirt::Error => e
raise Errors::UpdateServerError, error_message: e.message
end
begin
# need to check whether the updated XML contains all the changes requested
proposed = VagrantPlugins::ProviderLibvirt::Util::Xml.new(new_xml)
applied = VagrantPlugins::ProviderLibvirt::Util::Xml.new(libvirt_domain.xml_desc(1))
if proposed != applied
require 'diffy'
diff = Diffy::Diff.new(proposed.to_str, applied.to_str, :context => 3).to_s(:text)
error_msg = "Libvirt failed to fully update the domain with the specified XML. Result differs from requested:\n" +
"--- requested\n+++ result\n#{diff}\n" +
"Typically this means there is a bug in the XML being sent, please log an issue"
raise Errors::UpdateServerError, error_message: error_msg
end
rescue Exception => e
env[:machine].provider.driver.connection.define_domain(descr)
raise
end
end
rescue Errors::VagrantLibvirtError => e
# providing XML for the same name and UUID will update the existing domain
libvirt_domain = env[:machine].provider.driver.connection.define_domain(new_xml)
rescue ::Libvirt::Error => e
env[:ui].error("Error when updating domain settings: #{e.message}")
raise
raise Errors::UpdateServerError, error_message: e.message
end
# need to check whether the updated XML contains all the changes requested
proposed = VagrantPlugins::ProviderLibvirt::Util::Xml.new(new_xml)
applied = VagrantPlugins::ProviderLibvirt::Util::Xml.new(libvirt_domain.xml_desc(1))
if proposed != applied
require 'diffy'
diff = Diffy::Diff.new(proposed.to_str, applied.to_str, :context => 3).to_s(:text)
error_msg = "Libvirt failed to fully update the domain with the specified XML. Result differs from requested:\n" +
"--- requested\n+++ result\n#{diff}\n" +
"Typically this means there is a bug in the XML being sent, please log an issue"
env[:ui].error("Updated domain settings did not fully apply, attempting restore to previous definition: #{error_msg}")
begin
env[:machine].provider.driver.connection.define_domain(descr)
rescue Fog::Errors::Error => e
env[:ui].error("Failed to restore previous domain definition: #{e.message}")
end
raise Errors::UpdateServerError, error_message: error_msg
end
end
begin
# Autostart with host if enabled in Vagrantfile
libvirt_domain.autostart = config.autostart
@logger.debug {
@ -462,7 +461,7 @@ module VagrantPlugins
env[:ui].info(I18n.t('vagrant_libvirt.starting_domain'))
domain.start
rescue Fog::Errors::Error, Errors::VagrantLibvirtError => e
raise Errors::FogError, message: e.message
raise Errors::DomainStartError, error_message: e.message
end
@app.call(env)

View File

@ -178,6 +178,10 @@ module VagrantPlugins
error_key(:no_domain_error)
end
class DomainStartError < VagrantLibvirtError
error_key(:domain_start_error)
end
class AttachDeviceError < VagrantLibvirtError
error_key(:attach_device_error)
end

View File

@ -153,6 +153,8 @@ en:
Error while downloading volume '%{volume_name}' from storage pool '%{pool_name}': %{error_message}
no_domain_error: |-
No domain found. %{error_message}
domain_start_error: |-
Failed to start domain: %{error_message}
attach_device_error: |-
Error while attaching new device to domain. %{error_message}
detach_device_error: |-

View File

@ -94,7 +94,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(domain).to_not receive(:start)
expect { subject.call(env) }.to raise_error(VagrantPlugins::ProviderLibvirt::Errors::FogError)
expect { subject.call(env) }.to raise_error(VagrantPlugins::ProviderLibvirt::Errors::UpdateServerError)
end
end