Files
vagrant-libvirt/spec/unit/action/start_domain_spec.rb
Darragh Bailey 7d48c5ec46 Make failure to apply XML domain update non fatal (#1581)
As this is the first time people may be alerted to an issue applying
changes, given that previously they would be silently discarded, better
to switch to warning the end user rather than blocking the domain start.
This is no worse than the previous position, and allows previous
configurations that were understood to be working, to continue to work
as they are, with hopefully issues logged by users that will allow
more correct XML to be sent before making failure to match the changes
applied fatal.
2022-09-07 10:35:25 +00:00

373 lines
13 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
require 'support/sharedcontext'
require 'support/libvirt_context'
require 'vagrant-libvirt/errors'
require 'vagrant-libvirt/action/start_domain'
describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
subject { described_class.new(app, env) }
include_context 'unit'
include_context 'libvirt'
let(:servers) { double('servers') }
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)) }
before do
allow(driver).to receive(:created?).and_return(true)
end
describe '#call' do
let(:test_file) { 'default.xml' }
before do
allow(connection).to receive(:client).and_return(libvirt_client)
allow(libvirt_client).to receive(:lookup_domain_by_uuid).and_return(libvirt_domain)
allow(connection).to receive(:servers).and_return(servers)
allow(servers).to receive(:get).and_return(domain)
allow(logger).to receive(:debug)
allow(logger).to receive(:info)
allow(libvirt_domain).to receive(:xml_desc).and_return(domain_xml)
allow(libvirt_domain).to receive(:max_memory).and_return(512*1024)
allow(libvirt_domain).to receive(:num_vcpus).and_return(1)
end
it 'should execute without changing' do
expect(ui).to_not receive(:error)
expect(libvirt_client).to_not receive(:define_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
context 'when xml is formatted differently' do
let(:test_file) { 'default_with_different_formatting.xml' }
let(:updated_domain_xml) {
new_xml = domain_xml.dup
new_xml.gsub!(/<cpu .*<\/cpu>/m, '<cpu check="none" mode="host-passthrough"/>')
new_xml
}
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.cpu_mode = "host-passthrough"
EOF
end
it 'should correctly detect the domain was updated' do
expect(ui).to_not receive(:error)
expect(libvirt_domain).to receive(:autostart=)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'when xml not applied' do
let(:test_file) { 'default_with_different_formatting.xml' }
let(:updated_domain_xml) {
new_xml = domain_xml.dup
new_xml.gsub!(/<cpu .*<\/cpu>/m, '<cpu mode="host-passthrough"/>')
new_xml
}
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.cpu_mode = "host-passthrough"
EOF
end
it 'should error and revert the update' do
expect(ui).to receive(:warn).with(/\+ <cpu mode="host-passthrough" \/>.*Typically this means there is a bug in the XML being sent, please log an issue/m)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
#expect(connection).to receive(:define_domain).with(domain_xml) # undo
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::UpdateServerError)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'when any setting changed' do
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.cpus = 2
EOF
end
let(:updated_domain_xml) {
new_xml = domain_xml.dup
new_xml['<vcpu>1</vcpu>'] = '<vcpu>2</vcpu>'
new_xml
}
it 'should update the domain' do
expect(ui).to_not receive(:error)
expect(libvirt_domain).to receive(:autostart=)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
context 'when there is an error during update' do
it 'should skip attempting to start' do
expect(ui).to receive(:error)
expect(connection).to receive(:define_domain).and_raise(::Libvirt::Error)
expect { subject.call(env) }.to raise_error(VagrantPlugins::ProviderLibvirt::Errors::VagrantLibvirtError)
end
end
context 'when there is an interrupt' do
it 'should skip attempting to start' do
expect(connection).to receive(:define_domain).and_raise(Interrupt)
expect { subject.call(env) }.to raise_error(Interrupt)
end
end
end
context 'nvram' do
context 'when being added to existing' do
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.loader = "/path/to/loader/file"
libvirt.nvram = "/path/to/nvram/file"
EOF
end
let(:test_file) { 'existing.xml' }
let(:updated_test_file) { 'existing_added_nvram.xml' }
it 'should add the nvram element' do
expect(ui).to_not receive(:error)
expect(connection).to receive(:define_domain).with(updated_domain_xml).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'when it was already in use' do
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.loader = "/path/to/loader/file"
libvirt.nvram = "/path/to/nvram/file1"
EOF
end
let(:test_file) { 'nvram_domain.xml' }
let(:updated_test_file) { 'nvram_domain_other_setting.xml' }
it 'should keep the XML element' do
expect(ui).to_not receive(:error)
expect(connection).to receive(:define_domain).with(updated_domain_xml).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
context 'when it is being disabled' do
let(:vagrantfile_providerconfig) { }
let(:updated_test_file) { 'nvram_domain_removed.xml' }
it 'should delete the XML element' do
expect(ui).to_not receive(:error)
expect(connection).to receive(:define_domain).with(updated_domain_xml).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_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
context 'tpm' do
context 'passthrough tpm 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(ui).to_not receive(:error)
expect(logger).to receive(:debug).with('tpm config changed')
expect(connection).to receive(:define_domain).with(updated_domain_xml).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'emulated tpm added' do
let(:updated_test_file) { 'default_added_tpm_version.xml' }
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.tpm_type = 'emulator'
libvirt.tpm_model = 'tpm-crb'
libvirt.tpm_version = '2.0'
EOF
end
it 'should modify the domain tpm_path' do
expect(ui).to_not receive(:error)
expect(logger).to receive(:debug).with('tpm config changed')
expect(connection).to receive(:define_domain).with(updated_domain_xml).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'same passthrough tpm config' do
let(:test_file) { 'default_added_tpm_path.xml' }
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 execute without changing' do
expect(ui).to_not receive(:error)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'same emulated tpm config' do
let(:test_file) { 'default_added_tpm_version.xml' }
let(:updated_test_file) { 'default_added_tpm_version.xml' }
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.tpm_type = 'emulator'
libvirt.tpm_model = 'tpm-crb'
libvirt.tpm_version = '2.0'
EOF
end
it 'should execute without changing' do
expect(ui).to_not receive(:error)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'change from passthrough to emulated' do
let(:test_file) { 'default_added_tpm_path.xml' }
let(:updated_test_file) { 'default_added_tpm_version.xml' }
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.tpm_type = 'emulator'
libvirt.tpm_model = 'tpm-crb'
libvirt.tpm_version = '2.0'
EOF
end
it 'should modify the domain' do
expect(ui).to_not receive(:error)
expect(logger).to receive(:debug).with('tpm config changed')
expect(connection).to receive(:define_domain).with(updated_domain_xml).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_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
context 'clock_timers' do
let(:test_file) { 'clock_timer_rtc.xml' }
context 'timers unchanged' do
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.clock_timer(:name => "rtc")
EOF
end
it 'should not modify the domain' do
expect(ui).to_not receive(:error)
expect(logger).to_not receive(:debug).with('clock timers config changed')
expect(connection).to_not receive(:define_domain)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'timers added' do
let(:vagrantfile_providerconfig) do
<<-EOF
libvirt.clock_timer(:name => "rtc")
libvirt.clock_timer(:name => "tsc")
EOF
end
let(:updated_test_file) { 'clock_timer_rtc_tsc.xml' }
it 'should modify the domain' do
expect(ui).to_not receive(:error)
expect(logger).to receive(:debug).with('clock timers config changed')
expect(connection).to receive(:define_domain).with(match(/<clock offset='utc'>\s*<timer name='rtc'\/>\s*<timer name='tsc'\/>\s*<\/clock>/)).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'timers removed' do
let(:updated_test_file) { 'clock_timer_removed.xml' }
it 'should modify the domain' do
expect(ui).to_not receive(:error)
expect(logger).to receive(:debug).with('clock timers config changed')
expect(connection).to receive(:define_domain).with(match(/<clock offset='utc'>\s*<\/clock>/)).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_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