Reduce start domain unnecessary domain redefines (#1178)

Various checks in the start domain action were accidentally causing a
redefine right after initial create. Update to provide debug output when
the domain needs to be changed to allow capture of the redefines
occurring in the future and to make it easy for the tests to pick up
where the redefine was triggered by setting an expectation on the log
output.

Include a small fix to avoid running strip on what might be a nil object
returned for elements without any text attributes.

Fix a bug where changes to tpm settings made to the config after an
initial domain creation where there was previously no tpm defined, would
be ignored.

Adds a logger double and updates other tests that trigger log calls.

should fix #1176
This commit is contained in:
Darragh Bailey
2020-12-05 15:24:42 +00:00
committed by GitHub
parent c12b0de254
commit 21f7a796ff
8 changed files with 130 additions and 15 deletions

View File

@@ -43,6 +43,7 @@ module VagrantPlugins
bus = disk[:bus] bus = disk[:bus]
REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="' + device + '"]') do |disk_target| REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="' + device + '"]') do |disk_target|
next unless disk_target.attributes['bus'] != bus next unless disk_target.attributes['bus'] != bus
@logger.debug "disk #{device} bus updated from '#{disk_target.attributes['bus']}' to '#{bus}'"
descr_changed = true descr_changed = true
disk_target.attributes['bus'] = bus disk_target.attributes['bus'] = bus
disk_target.parent.delete_element("#{disk_target.parent.xpath}/address") disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
@@ -52,6 +53,7 @@ module VagrantPlugins
# disk_bus # disk_bus
REXML::XPath.each(xml_descr, '/domain/devices/disk[@device="disk"]/target[@dev="vda"]') do |disk_target| 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 next unless disk_target.attributes['bus'] != config.disk_bus
@logger.debug "domain disk bus updated from '#{disk_target.attributes['bus']}' to '#{bus}'"
descr_changed = true descr_changed = true
disk_target.attributes['bus'] = config.disk_bus disk_target.attributes['bus'] = config.disk_bus
disk_target.parent.delete_element("#{disk_target.parent.xpath}/address") disk_target.parent.delete_element("#{disk_target.parent.xpath}/address")
@@ -61,6 +63,7 @@ module VagrantPlugins
unless config.nic_model_type.nil? unless config.nic_model_type.nil?
REXML::XPath.each(xml_descr, '/domain/devices/interface/model') do |iface_model| REXML::XPath.each(xml_descr, '/domain/devices/interface/model') do |iface_model|
if iface_model.attributes['type'] != config.nic_model_type 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 descr_changed = true
iface_model.attributes['type'] = config.nic_model_type iface_model.attributes['type'] = config.nic_model_type
end end
@@ -68,7 +71,9 @@ module VagrantPlugins
end end
# vCpu count # vCpu count
if config.cpus.to_i != libvirt_domain.num_vcpus(0) 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 descr_changed = true
REXML::XPath.first(xml_descr, '/domain/vcpu').text = config.cpus REXML::XPath.first(xml_descr, '/domain/vcpu').text = config.cpus
end end
@@ -76,11 +81,13 @@ module VagrantPlugins
# cpu_mode # cpu_mode
cpu = REXML::XPath.first(xml_descr, '/domain/cpu') cpu = REXML::XPath.first(xml_descr, '/domain/cpu')
if cpu.nil? if cpu.nil?
@logger.debug "cpu_mode updated from not set to '#{config.cpu_mode}'"
descr_changed = true descr_changed = true
cpu = REXML::Element.new('cpu', REXML::XPath.first(xml_descr, '/domain')) cpu = REXML::Element.new('cpu', REXML::XPath.first(xml_descr, '/domain'))
cpu.attributes['mode'] = config.cpu_mode cpu.attributes['mode'] = config.cpu_mode
else else
if cpu.attributes['mode'] != config.cpu_mode if cpu.attributes['mode'] != config.cpu_mode
@logger.debug "cpu_mode updated from '#{cpu.attributes['mode']}' to '#{config.cpu_mode}'"
descr_changed = true descr_changed = true
cpu.attributes['mode'] = config.cpu_mode cpu.attributes['mode'] = config.cpu_mode
end end
@@ -89,16 +96,19 @@ module VagrantPlugins
if config.cpu_mode != 'host-passthrough' if config.cpu_mode != 'host-passthrough'
cpu_model = REXML::XPath.first(xml_descr, '/domain/cpu/model') cpu_model = REXML::XPath.first(xml_descr, '/domain/cpu/model')
if cpu_model.nil? if cpu_model.nil?
@logger.debug "cpu_model updated from not set to '#{config.cpu_model}'"
descr_changed = true descr_changed = true
cpu_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/cpu')) cpu_model = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/cpu'))
cpu_model.attributes['fallback'] = 'allow' cpu_model.attributes['fallback'] = 'allow'
cpu_model.text = config.cpu_model cpu_model.text = config.cpu_model
else else
if cpu_model.text.strip != config.cpu_model.strip 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 descr_changed = true
cpu_model.text = config.cpu_model cpu_model.text = config.cpu_model
end end
if cpu_model.attributes['fallback'] != config.cpu_fallback 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 descr_changed = true
cpu_model.attributes['fallback'] = config.cpu_fallback cpu_model.attributes['fallback'] = config.cpu_fallback
end end
@@ -107,12 +117,14 @@ module VagrantPlugins
svm_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="svm"]') svm_feature = REXML::XPath.first(xml_descr, '/domain/cpu/feature[@name="svm"]')
if config.nested if config.nested
if vmx_feature.nil? if vmx_feature.nil?
@logger.debug "nested mode enabled from unset by setting cpu vmx feature"
descr_changed = true descr_changed = true
vmx_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu')) vmx_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
vmx_feature.attributes['policy'] = 'optional' vmx_feature.attributes['policy'] = 'optional'
vmx_feature.attributes['name'] = 'vmx' vmx_feature.attributes['name'] = 'vmx'
end end
if svm_feature.nil? if svm_feature.nil?
@logger.debug "nested mode enabled from unset by setting cpu svm feature"
descr_changed = true descr_changed = true
svm_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu')) svm_feature = REXML::Element.new('feature', REXML::XPath.first(xml_descr, '/domain/cpu'))
svm_feature.attributes['policy'] = 'optional' svm_feature.attributes['policy'] = 'optional'
@@ -120,16 +132,19 @@ module VagrantPlugins
end end
else else
unless vmx_feature.nil? unless vmx_feature.nil?
@logger.debug "nested mode disabled for cpu by removing vmx feature"
descr_changed = true descr_changed = true
cpu.delete_element(vmx_feature) cpu.delete_element(vmx_feature)
end end
unless svm_feature.nil? unless svm_feature.nil?
@logger.debug "nested mode disabled for cpu by removing svm feature"
descr_changed = true descr_changed = true
cpu.delete_element(svm_feature) cpu.delete_element(svm_feature)
end end
end end
elsif config.numa_nodes == nil elsif config.numa_nodes == nil
unless cpu.elements.to_a.empty? unless cpu.elements.to_a.empty?
@logger.debug "switching cpu_mode to host-passthrough and removing emulated cpu features"
descr_changed = true descr_changed = true
cpu.elements.each do |elem| cpu.elements.each do |elem|
cpu.delete_element(elem) cpu.delete_element(elem)
@@ -183,8 +198,9 @@ module VagrantPlugins
tpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm') tpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm')
if tpm.nil? if tpm.nil?
@logger.debug "tpm created from previously not defined"
descr_changed = true descr_changed = true
tpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices/tpm/model')) tpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices'))
tpm.attributes['model'] = config.tpm_model tpm.attributes['model'] = config.tpm_model
tpm_backend_type = tpm.add_element('backend') tpm_backend_type = tpm.add_element('backend')
tpm_backend_type.attributes['type'] = config.tpm_type tpm_backend_type.attributes['type'] = config.tpm_type
@@ -192,16 +208,20 @@ module VagrantPlugins
tpm_device_path.attributes['path'] = config.tpm_path tpm_device_path.attributes['path'] = config.tpm_path
else else
if tpm.attributes['model'] != config.tpm_model if tpm.attributes['model'] != config.tpm_model
@logger.debug "tpm model updated from '#{tpm.attributes['model']}' to '#{config.tpm_model}'"
descr_changed = true descr_changed = true
tpm.attributes['model'] = config.tpm_model tpm.attributes['model'] = config.tpm_model
end end
if tpm.elements['backend'].attributes['type'] != config.tpm_type backend = tpm.elements['backend']
if backend.attributes['type'] != config.tpm_type
@logger.debug "tpm type updated from '#{backend.attributes['type']}' to '#{config.tpm_type}'"
descr_changed = true descr_changed = true
tpm.elements['backend'].attributes['type'] = config.tpm_type backend.attributes['type'] = config.tpm_type
end end
if tpm.elements['backend'].elements['device'].attributes['path'] != config.tpm_path if backend.elements['device'].attributes['path'] != config.tpm_path
@logger.debug "tpm path updated from '#{backend.elements['device'].attributes['path']}' to '#{config.tpm_path}'"
descr_changed = true descr_changed = true
tpm.elements['backend'].elements['device'].attributes['path'] = config.tpm_path backend.elements['device'].attributes['path'] = config.tpm_path
end end
end end
end end
@@ -210,16 +230,21 @@ module VagrantPlugins
video = REXML::XPath.first(xml_descr, '/domain/devices/video') video = REXML::XPath.first(xml_descr, '/domain/devices/video')
if !video.nil? && (config.graphics_type == 'none') if !video.nil? && (config.graphics_type == 'none')
# graphics_type = none, video devices are removed since there is no possible output # 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 descr_changed = true
video.parent.delete_element(video) video.parent.delete_element(video)
else else
video_model = REXML::XPath.first(xml_descr, '/domain/devices/video/model') video_model = REXML::XPath.first(xml_descr, '/domain/devices/video/model')
if video_model.nil? 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 = REXML::Element.new('model', REXML::XPath.first(xml_descr, '/domain/devices/video'))
video_model.attributes['type'] = config.video_type video_model.attributes['type'] = config.video_type
video_model.attributes['vram'] = config.video_vram video_model.attributes['vram'] = config.video_vram
else else
if video_model.attributes['type'] != config.video_type || video_model.attributes['vram'] != config.video_vram 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 descr_changed = true
video_model.attributes['type'] = config.video_type video_model.attributes['type'] = config.video_type
video_model.attributes['vram'] = config.video_vram video_model.attributes['vram'] = config.video_vram
@@ -237,11 +262,13 @@ module VagrantPlugins
if config.dtb if config.dtb
dtb = REXML::XPath.first(xml_descr, '/domain/os/dtb') dtb = REXML::XPath.first(xml_descr, '/domain/os/dtb')
if dtb.nil? if dtb.nil?
@logger.debug "dtb updated from not set to '#{config.dtb}'"
descr_changed = true descr_changed = true
dtb = REXML::Element.new('dtb', REXML::XPath.first(xml_descr, '/domain/os')) dtb = REXML::Element.new('dtb', REXML::XPath.first(xml_descr, '/domain/os'))
dtb.text = config.dtb dtb.text = config.dtb
else else
if dtb.text != config.dtb if (dtb.text or '') != config.dtb
@logger.debug "dtb updated from '#{dtb.text}' to '#{config.dtb}'"
descr_changed = true descr_changed = true
dtb.text = config.dtb dtb.text = config.dtb
end end
@@ -252,11 +279,13 @@ module VagrantPlugins
if config.kernel if config.kernel
kernel = REXML::XPath.first(xml_descr, '/domain/os/kernel') kernel = REXML::XPath.first(xml_descr, '/domain/os/kernel')
if kernel.nil? if kernel.nil?
@logger.debug "kernel updated from not set to '#{config.kernel}'"
descr_changed = true descr_changed = true
kernel = REXML::Element.new('kernel', REXML::XPath.first(xml_descr, '/domain/os')) kernel = REXML::Element.new('kernel', REXML::XPath.first(xml_descr, '/domain/os'))
kernel.text = config.kernel kernel.text = config.kernel
else else
if kernel.text != config.kernel if (kernel.text or '').strip != config.kernel
@logger.debug "kernel updated from '#{kernel.text}' to '#{config.kernel}'"
descr_changed = true descr_changed = true
kernel.text = config.kernel kernel.text = config.kernel
end end
@@ -265,11 +294,13 @@ module VagrantPlugins
if config.initrd if config.initrd
initrd = REXML::XPath.first(xml_descr, '/domain/os/initrd') initrd = REXML::XPath.first(xml_descr, '/domain/os/initrd')
if initrd.nil? if initrd.nil?
@logger.debug "initrd updated from not set to '#{config.initrd}'"
descr_changed = true descr_changed = true
initrd = REXML::Element.new('initrd', REXML::XPath.first(xml_descr, '/domain/os')) initrd = REXML::Element.new('initrd', REXML::XPath.first(xml_descr, '/domain/os'))
initrd.text = config.initrd initrd.text = config.initrd
else else
if initrd.text != config.initrd if (initrd.text or '').strip != config.initrd
@logger.debug "initrd updated from '#{initrd.text}' to '#{config.initrd}'"
descr_changed = true descr_changed = true
initrd.text = config.initrd initrd.text = config.initrd
end end

View File

@@ -7,6 +7,7 @@ shared_context 'libvirt' do
let(:id) { 'dummy-vagrant_dummy' } let(:id) { 'dummy-vagrant_dummy' }
let(:connection) { double('connection') } let(:connection) { double('connection') }
let(:domain) { double('domain') } let(:domain) { double('domain') }
let(:logger) { double('logger') }
def connection_result(options = {}) def connection_result(options = {})
result = options.fetch(:result, nil) result = options.fetch(:result, nil)
@@ -26,5 +27,6 @@ shared_context 'libvirt' do
allow(domain).to receive(:mac).and_return('9C:D5:53:F1:5A:E7') allow(domain).to receive(:mac).and_return('9C:D5:53:F1:5A:E7')
allow(machine).to receive(:id).and_return(id) allow(machine).to receive(:id).and_return(id)
allow(Log4r::Logger).to receive(:new).and_return(logger)
end end
end end

View File

@@ -3,10 +3,14 @@ require 'spec_helper'
shared_context 'unit' do shared_context 'unit' do
include_context 'vagrant-unit' include_context 'vagrant-unit'
let(:vagrantfile_providerconfig) { '' }
let(:vagrantfile) do let(:vagrantfile) do
<<-EOF <<-EOF
Vagrant.configure('2') do |config| Vagrant.configure('2') do |config|
config.vm.define :test config.vm.define :test
config.vm.provider :libvirt do |libvirt|
#{vagrantfile_providerconfig}
end
end end
EOF EOF
end end

View File

@@ -26,6 +26,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::CreateDomain do
allow(connection).to receive(:servers).and_return(servers) allow(connection).to receive(:servers).and_return(servers)
allow(connection).to receive(:volumes).and_return(volumes) allow(connection).to receive(:volumes).and_return(volumes)
allow(logger).to receive(:info)
end end
context 'default pool' do context 'default pool' do

View File

@@ -16,6 +16,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
let(:servers) { double('servers') } let(:servers) { double('servers') }
let(:domain_xml) { File.read(File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), test_file)) } let(:domain_xml) { File.read(File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), test_file)) }
let(:updated_domain_xml) { File.read(File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), updated_test_file)) }
describe '#call' do describe '#call' do
before do before do
@@ -26,6 +27,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
allow(connection).to receive(:servers).and_return(servers) allow(connection).to receive(:servers).and_return(servers)
allow(servers).to receive(:get).and_return(domain) allow(servers).to receive(:get).and_return(domain)
expect(logger).to receive(:info)
end end
context 'default config' do context 'default config' do
@@ -38,12 +40,35 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
allow(libvirt_domain).to receive(:num_vcpus).and_return(1) allow(libvirt_domain).to receive(:num_vcpus).and_return(1)
end end
it 'should execute correctly' do it 'should execute without changing' do
allow(libvirt_domain).to receive(:undefine)
expect(logger).to_not receive(:debug)
expect(libvirt_domain).to receive(:autostart=) expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start) expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil expect(subject.call(env)).to be_nil
end end
context 'tpm_path added' do
let(:updated_test_file) { 'default_added_tpm_path.xml' }
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.tpm_path = '/dev/tpm0'
libvirt.tpm_type = 'passthrough'
libvirt.tpm_model = 'tpm-tis'
EOF
end
it 'should modify the domain tpm_path' do
expect(libvirt_domain).to receive(:undefine)
expect(logger).to receive(:debug).with('tpm created from previously not defined')
expect(servers).to receive(:create).with(xml: updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
end end
end end
end end

View File

@@ -8,14 +8,14 @@
<cpu mode='host-model'> <cpu mode='host-model'>
<model fallback='allow'></model> <model fallback='allow'/>
</cpu> </cpu>
<os> <os>
<type>hvm</type> <type>hvm</type>
<kernel/> <kernel/>
<initrd></initrd> <initrd/>
<cmdline/> <cmdline/>
</os> </os>
<features> <features>

View File

@@ -0,0 +1,48 @@
<domain xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' type=''>
<name/>
<title/>
<description/>
<uuid/>
<memory/>
<vcpu>1</vcpu>
<cpu mode='host-model'>
<model fallback='allow'/>
</cpu>
<os>
<type>hvm</type>
<kernel/>
<initrd/>
<cmdline/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<devices>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target port='0'/>
</console>
<input bus='ps2' type='mouse'/>
<graphics autoport='yes' keymap='en-us' listen='127.0.0.1' port='-1' type='vnc'/>
<video>
<model heads='1' type='cirrus' vram='9216'/>
</video>
<tpm model='tpm-tis'><backend type='passthrough'><device path='/dev/tpm0'/></backend></tpm></devices>
</domain>

View File

@@ -41,6 +41,7 @@ describe VagrantPlugins::ProviderLibvirt::Action::WaitTillUp do
it 'should exit' do it 'should exit' do
expect(app).to_not receive(:call) expect(app).to_not receive(:call)
expect(ui).to receive(:info).with('Waiting for domain to get an IP address...') expect(ui).to receive(:info).with('Waiting for domain to get an IP address...')
expect(logger).to receive(:debug).with(/Searching for IP for MAC address: .*/)
expect(subject.call(env)).to be_nil expect(subject.call(env)).to be_nil
end end
end end
@@ -56,7 +57,6 @@ describe VagrantPlugins::ProviderLibvirt::Action::WaitTillUp do
expect(app).to_not receive(:call) expect(app).to_not receive(:call)
expect(ui).to receive(:info).with('Waiting for domain to get an IP address...') expect(ui).to receive(:info).with('Waiting for domain to get an IP address...')
expect(ui).to receive(:info).with('Waiting for SSH to become available...') expect(ui).to receive(:info).with('Waiting for SSH to become available...')
logger = subject.instance_variable_get(:@logger)
expect(logger).to receive(:debug).with(/Searching for IP for MAC address: .*/) expect(logger).to receive(:debug).with(/Searching for IP for MAC address: .*/)
expect(logger).to receive(:info).with('Got IP address 192.168.121.2') expect(logger).to receive(:info).with('Got IP address 192.168.121.2')
expect(logger).to receive(:info).with(/Time for getting IP: .*/) expect(logger).to receive(:info).with(/Time for getting IP: .*/)
@@ -77,6 +77,10 @@ describe VagrantPlugins::ProviderLibvirt::Action::WaitTillUp do
expect(app).to receive(:call) expect(app).to receive(:call)
expect(ui).to receive(:info).with('Waiting for domain to get an IP address...') expect(ui).to receive(:info).with('Waiting for domain to get an IP address...')
expect(ui).to receive(:info).with('Waiting for SSH to become available...') expect(ui).to receive(:info).with('Waiting for SSH to become available...')
expect(logger).to receive(:debug).with(/Searching for IP for MAC address: .*/)
expect(logger).to receive(:info).with('Got IP address 192.168.121.2')
expect(logger).to receive(:info).with(/Time for getting IP: .*/)
expect(logger).to receive(:info).with(/Time for SSH ready: .*/)
expect(env[:machine].communicate).to receive(:ready?).and_return(true) expect(env[:machine].communicate).to receive(:ready?).and_return(true)
expect(subject.call(env)).to be_nil expect(subject.call(env)).to be_nil
end end