Files
vagrant-libvirt/spec/unit/action/start_domain_spec.rb

582 lines
21 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require_relative '../../spec_helper'
require 'vagrant-libvirt/errors'
require 'vagrant-libvirt/action/start_domain'
require 'vagrant-libvirt/util/unindent'
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(ui).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(:warn)
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(:warn)
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 elements and attributes reordered' do
let(:test_file) { 'existing.xml' }
let(:updated_test_file) { 'existing_reordered.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(:warn)
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(:warn)
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 'cpu' do
let(:test_file) { 'existing.xml' }
let(:updated_domain_xml) {
new_xml = domain_xml.dup
new_xml.gsub!(
/<cpu .*\/>/,
<<-EOF
<cpu check='partial' mode='custom'>
<model fallback='allow'>Haswell</model>
<feature name='vmx' policy='optional'/>
<feature name='svm' policy='optional'/>
</cpu>
EOF
)
new_xml
}
let(:vagrantfile_providerconfig) {
<<-EOF
libvirt.cpu_mode = 'custom'
libvirt.cpu_model = 'Haswell'
libvirt.nested = true
EOF
}
it 'should set cpu related settings when changed' do
expect(ui).to_not receive(:warn)
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(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
let(:domain_xml_no_cpu) {
new_xml = domain_xml.dup
new_xml.gsub!(/<cpu .*\/>/, '')
new_xml
}
let(:updated_domain_xml_new_cpu) {
new_xml = domain_xml.dup
new_xml.gsub!(
/<cpu .*\/>/,
<<-EOF
<cpu mode='custom'>
<model fallback='allow'>Haswell</model>
<feature name='vmx' policy='optional'/>
<feature name='svm' policy='optional'/>
</cpu>
EOF
)
new_xml
}
it 'should add cpu settings if not already present' do
expect(ui).to_not receive(:warn)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml_no_cpu, updated_domain_xml_new_cpu)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
context 'launchSecurity' do
let(:updated_domain_xml_new_launch_security) {
new_xml = domain_xml.dup
new_xml.gsub!(
/<\/devices>/,
<<-EOF.unindent.rstrip
</devices>
<launchSecurity type='sev'>
<cbitpos>47</cbitpos>
<reducedPhysBits>1</reducedPhysBits>
<policy>0x0003</policy>
</launchSecurity>
EOF
)
new_xml
}
it 'should create if not already set' do
machine.provider_config.launchsecurity_data = {:type => 'sev', :cbitpos => 47, :reducedPhysBits => 1, :policy => "0x0003"}
expect(ui).to_not receive(:warn)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, updated_domain_xml_new_launch_security)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
context 'already exists' do
let(:domain_xml_launch_security) { updated_domain_xml_new_launch_security }
let(:updated_domain_xml_launch_security) {
new_xml = domain_xml_launch_security.dup
new_xml.gsub!(/<cbitpos>47/, '<cbitpos>48')
new_xml.gsub!(/<reducedPhysBits>1/, '<reducedPhysBits>2')
new_xml.gsub!(/<policy>0x0003/, '<policy>0x0004')
new_xml
}
it 'should update all settings' do
machine.provider_config.launchsecurity_data = {:type => 'sev', :cbitpos => 48, :reducedPhysBits => 2, :policy => "0x0004"}
expect(ui).to_not receive(:warn)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml_launch_security, updated_domain_xml_launch_security)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
it 'should remove if disabled' do
machine.provider_config.launchsecurity_data = nil
expect(ui).to_not receive(:warn)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml_launch_security, domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
context 'with controllers' do
# makes domain_xml contain 2 controllers and memballoon
# which should mean that launchsecurity element exists, but without
# iommu set on controllers
let(:test_file) { 'existing.xml' }
let(:updated_domain_xml_launch_security_controllers) {
new_xml = updated_domain_xml_new_launch_security.dup
new_xml.gsub!(
/<controller type='pci' index='0' model='pci-root'\/>/,
"<controller type='pci' index='0' model='pci-root'><driver iommu='on'/></controller>",
)
new_xml.gsub!(
/(<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'\/>)/,
'\1<driver iommu="on"/>',
)
# memballoon
new_xml.gsub!(
/(<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'\/>)/,
'\1<driver iommu="on"/>',
)
new_xml
}
it 'should set driver iommu on all controllers' do
machine.provider_config.launchsecurity_data = {:type => 'sev', :cbitpos => 47, :reducedPhysBits => 1, :policy => "0x0003"}
expect(ui).to_not receive(:warn)
expect(connection).to receive(:define_domain).and_return(libvirt_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml_launch_security, updated_domain_xml_launch_security_controllers)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(subject.call(env)).to be_nil
end
end
end
end
context 'graphics' do
context 'autoport not disabled' do
let(:test_file) { 'existing.xml' }
let(:launched_domain_xml) {
new_xml = domain_xml.dup
new_xml.gsub!(/graphics type='vnc' port='-1'/m, "graphics type='vnc' port='5900'")
new_xml
}
it 'should retrieve the port from XML' do
expect(ui).to_not receive(:warn)
expect(connection).to_not receive(:define_domain)
expect(libvirt_domain).to receive(:xml_desc).and_return(domain_xml, launched_domain_xml)
expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start)
expect(ui).to receive(:info).with(' -- Graphics Port: 5900')
expect(subject.call(env)).to be_nil
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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(:warn)
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