mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
Auto enable ssh connections if the ssh keyfile has been explicitly set and switch a number of settings from being explicitly set until after attempts to resolve the values have failed in order to allow decisions to be main on whether to set them based on inferred values only if not explicitly provided elsewhere. Add some additional tests and transport modes to expand the coverage of what is accepted to align as close to what libvirt will accept as possible.
457 lines
15 KiB
Ruby
457 lines
15 KiB
Ruby
require 'contextual_proc'
|
|
|
|
require 'spec_helper'
|
|
require 'support/sharedcontext'
|
|
|
|
require 'vagrant-libvirt/config'
|
|
|
|
describe VagrantPlugins::ProviderLibvirt::Config do
|
|
include_context 'unit'
|
|
|
|
let(:fake_env) { Hash.new }
|
|
|
|
describe '#clock_timer' do
|
|
it 'should handle all options' do
|
|
expect(
|
|
subject.clock_timer(
|
|
:name => 'rtc',
|
|
:track => 'wall',
|
|
:tickpolicy => 'delay',
|
|
:present => 'yes',
|
|
).length
|
|
).to be(1)
|
|
expect(
|
|
subject.clock_timer(
|
|
:name => 'tsc',
|
|
:tickpolicy => 'delay',
|
|
:frequency => '100',
|
|
:mode => 'auto',
|
|
:present => 'yes',
|
|
).length
|
|
).to be(2)
|
|
end
|
|
|
|
it 'should correctly save the options' do
|
|
opts = {:name => 'rtc', :track => 'wall'}
|
|
expect(subject.clock_timer(opts).length).to be(1)
|
|
|
|
expect(subject.clock_timers[0]).to eq(opts)
|
|
|
|
opts[:name] = 'tsc'
|
|
expect(subject.clock_timers[0]).to_not eq(opts)
|
|
end
|
|
|
|
it 'should error name option is missing' do
|
|
expect{ subject.clock_timer(:track => "wall") }.to raise_error("Clock timer name must be specified")
|
|
end
|
|
|
|
it 'should error if nil value for option supplied' do
|
|
expect{ subject.clock_timer(:name => "rtc", :track => nil) }.to raise_error("Value of timer option track is nil")
|
|
end
|
|
|
|
it 'should error if unrecognized option specified' do
|
|
expect{ subject.clock_timer(:name => "tsc", :badopt => "value") }.to raise_error("Unknown clock timer option: badopt")
|
|
end
|
|
end
|
|
|
|
describe '#finalize!' do
|
|
it 'is valid with defaults' do
|
|
subject.finalize!
|
|
end
|
|
|
|
context '@uri' do
|
|
before(:example) do
|
|
stub_const("ENV", fake_env)
|
|
fake_env['HOME'] = "/home/tests"
|
|
end
|
|
|
|
# table describing expected behaviour of inputs that affect the resulting uri as
|
|
# well as any subsequent settings that might be inferred if the uri was
|
|
# explicitly set.
|
|
[
|
|
# settings
|
|
[ # all default
|
|
{},
|
|
{:uri => "qemu:///system"},
|
|
],
|
|
|
|
# explicit uri settings
|
|
[ # transport and hostname
|
|
{:uri => "qemu+ssh://localhost/system"},
|
|
{:uri => "qemu+ssh://localhost/system", :connect_via_ssh => true, :host => "localhost", :username => nil},
|
|
],
|
|
[ # tcp transport with port
|
|
{:uri => "qemu+tcp://localhost:5000/system"},
|
|
{:uri => "qemu+tcp://localhost:5000/system", :connect_via_ssh => false, :host => "localhost", :username => nil},
|
|
],
|
|
[ # connect explicit to unix socket
|
|
{:uri => "qemu+unix:///system"},
|
|
{:uri => "qemu+unix:///system", :connect_via_ssh => false, :host => nil, :username => nil},
|
|
],
|
|
[ # via libssh2 should enable ssh as well
|
|
{:uri => "qemu+libssh2://user@remote/system?known_hosts=/home/user/.ssh/known_hosts"},
|
|
{
|
|
:uri => "qemu+libssh2://user@remote/system?known_hosts=/home/user/.ssh/known_hosts",
|
|
:connect_via_ssh => true, :host => "remote", :username => "user",
|
|
},
|
|
],
|
|
[ # xen
|
|
{:uri => "xen://remote/system?no_verify=1"},
|
|
{
|
|
:uri => "xen://remote/system?no_verify=1",
|
|
:connect_via_ssh => false, :host => "remote", :username => nil,
|
|
:id_ssh_key_file => nil,
|
|
},
|
|
{
|
|
:setup => ContextualProc.new {
|
|
expect(File).to_not receive(:file?)
|
|
}
|
|
}
|
|
],
|
|
[ # xen
|
|
{:uri => "xen+ssh://remote/system?no_verify=1"},
|
|
{
|
|
:uri => "xen+ssh://remote/system?no_verify=1",
|
|
:connect_via_ssh => true, :host => "remote", :username => nil,
|
|
:id_ssh_key_file => "/home/tests/.ssh/id_rsa",
|
|
},
|
|
{
|
|
:setup => ContextualProc.new {
|
|
expect(File).to receive(:file?).with("/home/tests/.ssh/id_rsa").and_return(true)
|
|
}
|
|
}
|
|
],
|
|
|
|
# with LIBVIRT_DEFAULT_URI
|
|
[ # all other set to default
|
|
{},
|
|
{:uri => "custom:///custom_path", :qemu_use_session => false},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => "custom:///custom_path"},
|
|
}
|
|
],
|
|
[ # with session
|
|
{},
|
|
{:uri => "qemu:///session", :qemu_use_session => true},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => "qemu:///session"},
|
|
}
|
|
],
|
|
[ # with session and using ssh infer connect by ssh and ignore host as not provided
|
|
{},
|
|
{:uri => "qemu+ssh:///session", :qemu_use_session => true, :connect_via_ssh => true, :host => nil},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => "qemu+ssh:///session"},
|
|
}
|
|
],
|
|
[ # with session and using ssh to specific host with additional query options provided, infer host and ssh
|
|
{},
|
|
{:uri => "qemu+ssh://remote/session?keyfile=my_id_rsa", :qemu_use_session => true, :connect_via_ssh => true, :host => 'remote'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => "qemu+ssh://remote/session?keyfile=my_id_rsa"},
|
|
}
|
|
],
|
|
[ # when session not set
|
|
{},
|
|
{:uri => "qemu:///system", :qemu_use_session => false},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => "qemu:///system"},
|
|
}
|
|
],
|
|
[ # when session appearing elsewhere
|
|
{},
|
|
{:uri => "qemu://remote/system?keyfile=my_session_id", :qemu_use_session => false},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => "qemu://remote/system?keyfile=my_session_id"},
|
|
}
|
|
],
|
|
|
|
# ignore LIBVIRT_DEFAULT_URI due to explicit settings
|
|
[ # when uri explicitly set
|
|
{:uri => 'qemu:///system'},
|
|
{:uri => 'qemu:///system'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => 'qemu://session'},
|
|
}
|
|
],
|
|
[ # when host explicitly set
|
|
{:host => 'remote'},
|
|
{:uri => 'qemu://remote/system'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => 'qemu://session'},
|
|
}
|
|
],
|
|
[ # when connect_via_ssh explicitly set
|
|
{:connect_via_ssh => true},
|
|
{:uri => 'qemu+ssh://localhost/system?no_verify=1'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => 'qemu://session'},
|
|
}
|
|
],
|
|
[ # when username explicitly set without ssh
|
|
{:username => 'my_user' },
|
|
{:uri => 'qemu:///system'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => 'qemu://session'},
|
|
}
|
|
],
|
|
[ # when username explicitly set with host but without ssh
|
|
{:username => 'my_user', :host => 'remote'},
|
|
{:uri => 'qemu://remote/system'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => 'qemu://session'},
|
|
}
|
|
],
|
|
[ # when password explicitly set
|
|
{:password => 'some_password'},
|
|
{:uri => 'qemu:///system', :password => 'some_password'},
|
|
{
|
|
:env => {'LIBVIRT_DEFAULT_URI' => 'qemu://session'},
|
|
}
|
|
],
|
|
|
|
# driver settings
|
|
[ # set to kvm only
|
|
{:driver => 'kvm'},
|
|
{:uri => "qemu:///system"},
|
|
],
|
|
[ # set to qemu only
|
|
{:driver => 'qemu'},
|
|
{:uri => "qemu:///system"},
|
|
],
|
|
[ # set to qemu with session enabled
|
|
{:driver => 'qemu', :qemu_use_session => true},
|
|
{:uri => "qemu:///session"},
|
|
],
|
|
[ # set to openvz only
|
|
{:driver => 'openvz'},
|
|
{:uri => "openvz:///system"},
|
|
],
|
|
[ # set to esx
|
|
{:driver => 'esx'},
|
|
{:uri => "esx:///"},
|
|
],
|
|
[ # set to vbox only
|
|
{:driver => 'vbox'},
|
|
{:uri => "vbox:///session"},
|
|
],
|
|
|
|
# connect_via_ssh settings
|
|
[ # enabled
|
|
{:connect_via_ssh => true},
|
|
{:uri => "qemu+ssh://localhost/system?no_verify=1"},
|
|
],
|
|
[ # enabled with user
|
|
{:connect_via_ssh => true, :username => 'my_user'},
|
|
{:uri => "qemu+ssh://my_user@localhost/system?no_verify=1"},
|
|
],
|
|
[ # enabled with host
|
|
{:connect_via_ssh => true, :host => 'remote_server'},
|
|
{:uri => "qemu+ssh://remote_server/system?no_verify=1"},
|
|
],
|
|
|
|
# id_ssh_key_file behaviour
|
|
[ # set should take given value
|
|
{:connect_via_ssh => true, :id_ssh_key_file => '/path/to/keyfile'},
|
|
{:uri => 'qemu+ssh://localhost/system?no_verify=1&keyfile=/path/to/keyfile', :connect_via_ssh => true},
|
|
],
|
|
[ # set should infer use of ssh
|
|
{:id_ssh_key_file => '/path/to/keyfile'},
|
|
{:uri => 'qemu+ssh://localhost/system?no_verify=1&keyfile=/path/to/keyfile', :connect_via_ssh => true},
|
|
],
|
|
[ # connect_via_ssh should enable default but ignore due to not existing
|
|
{:connect_via_ssh => true},
|
|
{:uri => 'qemu+ssh://localhost/system?no_verify=1', :id_ssh_key_file => nil},
|
|
{
|
|
:setup => ContextualProc.new {
|
|
expect(File).to receive(:file?).with("/home/tests/.ssh/id_rsa").and_return(false)
|
|
}
|
|
}
|
|
],
|
|
[ # connect_via_ssh should enable default and include due to existing
|
|
{:connect_via_ssh => true},
|
|
{:uri => 'qemu+ssh://localhost/system?no_verify=1&keyfile=/home/tests/.ssh/id_rsa', :id_ssh_key_file => '/home/tests/.ssh/id_rsa'},
|
|
{
|
|
:setup => ContextualProc.new {
|
|
expect(File).to receive(:file?).with("/home/tests/.ssh/id_rsa").and_return(true)
|
|
}
|
|
}
|
|
],
|
|
|
|
# socket behaviour
|
|
[ # set
|
|
{:socket => '/var/run/libvirt/libvirt-sock'},
|
|
{:uri => "qemu:///system?socket=/var/run/libvirt/libvirt-sock"},
|
|
],
|
|
].each do |inputs, outputs, options|
|
|
opts = {}
|
|
opts.merge!(options) if options
|
|
|
|
it "should handle inputs #{inputs} with env (#{opts[:env]})" do
|
|
# allow some of these to fail for now if marked as such
|
|
if !opts[:allow_failure].nil?
|
|
pending(opts[:allow_failure])
|
|
end
|
|
|
|
if !opts[:setup].nil?
|
|
opts[:setup].apply(binding)
|
|
end
|
|
|
|
inputs.each do |k, v|
|
|
subject.instance_variable_set("@#{k}", v)
|
|
end
|
|
|
|
if !opts[:env].nil?
|
|
opts[:env].each do |k, v|
|
|
fake_env[k] = v
|
|
end
|
|
end
|
|
|
|
subject.finalize!
|
|
|
|
# ensure failed output indicates which settings are incorrect in the failed test
|
|
got = subject.instance_variables.each_with_object({}) do |name, hash|
|
|
if outputs.key?(name.to_s[1..-1].to_sym)
|
|
hash["#{name.to_s[1..-1]}".to_sym] =subject.instance_variable_get(name)
|
|
end
|
|
end
|
|
expect(got).to eq(outputs)
|
|
end
|
|
end
|
|
|
|
context 'when invalid @driver is defined' do
|
|
it "should raise exception for unrecognized" do
|
|
subject.driver = "bad-driver"
|
|
|
|
expect { subject.finalize! }.to raise_error("Require specify driver bad-driver")
|
|
end
|
|
end
|
|
|
|
context 'when invalid @uri is defined' do
|
|
it "should raise exception for unrecognized" do
|
|
subject.uri = "://bad-uri"
|
|
|
|
expect { subject.finalize! }.to raise_error("@uri set to invalid uri '://bad-uri'")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def assert_invalid
|
|
subject.finalize!
|
|
errors = subject.validate(machine)
|
|
raise "No errors: #{errors.inspect}" if errors.values.all?(&:empty?)
|
|
end
|
|
|
|
def assert_valid
|
|
subject.finalize!
|
|
errors = subject.validate(machine)
|
|
raise "Errors: #{errors.inspect}" unless errors.values.all?(&:empty?)
|
|
end
|
|
|
|
describe '#validate' do
|
|
it 'is valid with defaults' do
|
|
assert_valid
|
|
end
|
|
|
|
context 'with disks defined' do
|
|
before { expect(machine).to receive(:provider_config).and_return(subject).at_least(:once) }
|
|
|
|
it 'is valid if relative path used for disk' do
|
|
subject.storage :file, path: '../path/to/file.qcow2'
|
|
assert_valid
|
|
end
|
|
|
|
it 'should be invalid if absolute path used for disk' do
|
|
subject.storage :file, path: '/absolute/path/to/file.qcow2'
|
|
assert_invalid
|
|
end
|
|
end
|
|
|
|
context 'with mac defined' do
|
|
let (:vm) { double('vm') }
|
|
before { expect(machine.config).to receive(:vm).and_return(vm) }
|
|
|
|
it 'is valid with valid mac' do
|
|
expect(vm).to receive(:networks).and_return([[:public, { mac: 'aa:bb:cc:dd:ee:ff' }]])
|
|
assert_valid
|
|
end
|
|
|
|
it 'is valid with MAC containing no delimiters' do
|
|
network = [:public, { mac: 'aabbccddeeff' }]
|
|
expect(vm).to receive(:networks).and_return([network])
|
|
assert_valid
|
|
expect(network[1][:mac]).to eql('aa:bb:cc:dd:ee:ff')
|
|
end
|
|
|
|
it 'should be invalid if MAC not formatted correctly' do
|
|
expect(vm).to receive(:networks).and_return([[:public, { mac: 'aa/bb/cc/dd/ee/ff' }]])
|
|
assert_invalid
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#merge' do
|
|
let(:one) { described_class.new }
|
|
let(:two) { described_class.new }
|
|
|
|
subject { one.merge(two) }
|
|
|
|
context 'storage' do
|
|
context 'with disks' do
|
|
context 'assigned specific devices' do
|
|
it 'should merge disks with specific devices' do
|
|
one.storage(:file, device: 'vdb')
|
|
two.storage(:file, device: 'vdc')
|
|
subject.finalize!
|
|
expect(subject.disks).to include(include(device: 'vdb'),
|
|
include(device: 'vdc'))
|
|
end
|
|
end
|
|
|
|
context 'without devices given' do
|
|
it 'should merge disks with different devices assigned automatically' do
|
|
one.storage(:file)
|
|
two.storage(:file)
|
|
subject.finalize!
|
|
expect(subject.disks).to include(include(device: 'vdb'),
|
|
include(device: 'vdc'))
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with cdroms only' do
|
|
context 'assigned specific devs' do
|
|
it 'should merge disks with specific devices' do
|
|
one.storage(:file, device: :cdrom, dev: 'hda')
|
|
two.storage(:file, device: :cdrom, dev: 'hdb')
|
|
subject.finalize!
|
|
expect(subject.cdroms).to include(include(dev: 'hda'),
|
|
include(dev: 'hdb'))
|
|
end
|
|
end
|
|
|
|
context 'without devs given' do
|
|
it 'should merge cdroms with different devs assigned automatically' do
|
|
one.storage(:file, device: :cdrom)
|
|
two.storage(:file, device: :cdrom)
|
|
subject.finalize!
|
|
expect(subject.cdroms).to include(include(dev: 'hda'),
|
|
include(dev: 'hdb'))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'clock_timers' do
|
|
it 'should merge clock_timers' do
|
|
one.clock_timer(:name => 'rtc', :tickpolicy => 'catchup')
|
|
two.clock_timer(:name => 'hpet', :present => 'no')
|
|
|
|
expect(subject.clock_timers).to include(include(name: 'rtc'),
|
|
include(name: 'hpet'))
|
|
end
|
|
end
|
|
end
|
|
end
|