Files
vagrant-libvirt/spec/unit/config_spec.rb
Darragh Bailey 18ebb9d9ed Enable frozen string across project (#1319)
Turn on frozen string support in all files by using a comment to avoid
enabling across dependencies where it may not work.

Fixes: #1177
2021-06-30 13:27:03 +01:00

557 lines
18 KiB
Ruby

# frozen_string_literal: true
require 'support/binding_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 => ProcWithBinding.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 => ProcWithBinding.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 => ProcWithBinding.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 => ProcWithBinding.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(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
context '@proxy_command' do
before(:example) do
stub_const("ENV", fake_env)
fake_env['HOME'] = "/home/tests"
end
[
# no connect_via_ssh
[
{:host => "remote"},
nil,
],
# connect_via_ssh
[ # host
{:connect_via_ssh => true, :host => 'remote'},
"ssh 'remote' -W %h:%p",
],
[ # include user
{:connect_via_ssh => true, :host => 'remote', :username => 'myuser'},
"ssh 'remote' -l 'myuser' -W %h:%p",
],
[ # remote contains port
{:connect_via_ssh => true, :host => 'remote:2222'},
"ssh 'remote' -p 2222 -W %h:%p",
],
[ # include user and default ssh key exists
{:connect_via_ssh => true, :host => 'remote', :username => 'myuser'},
"ssh 'remote' -l 'myuser' -i '/home/tests/.ssh/id_rsa' -W %h:%p",
{
:setup => ProcWithBinding.new {
expect(File).to receive(:file?).with("/home/tests/.ssh/id_rsa").and_return(true)
}
}
],
# disable id_ssh_key_file
[
{:connect_via_ssh => true, :host => 'remote', :id_ssh_key_file => nil},
"ssh 'remote' -W %h:%p",
],
[ # include user
{:connect_via_ssh => true, :host => 'remote', :id_ssh_key_file => nil},
"ssh 'remote' -W %h:%p",
],
# use @uri
[
{:uri => 'qemu+ssh://remote/system'},
"ssh 'remote' -W %h:%p",
],
[
{:uri => 'qemu+ssh://myuser@remote/system'},
"ssh 'remote' -l 'myuser' -W %h:%p",
],
[
{:uri => 'qemu+ssh://remote/system?keyfile=/some/path/to/keyfile'},
"ssh 'remote' -i '/some/path/to/keyfile' -W %h:%p",
],
# provide custom template
[
{:connect_via_ssh => true, :host => 'remote', :proxy_command => "ssh {host} nc %h %p" },
"ssh remote nc %h %p",
],
[
{:connect_via_ssh => true, :host => 'remote', :username => 'myuser', :proxy_command => "ssh {host} nc %h %p" },
"ssh remote nc %h %p",
],
[
{:connect_via_ssh => true, :host => 'remote', :username => 'myuser', :proxy_command => "ssh {host} -l {username} nc %h %p" },
"ssh remote -l myuser nc %h %p",
],
].each do |inputs, proxy_command, options|
opts = {}
opts.merge!(options) if options
it "should handle inputs #{inputs}" 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(binding)
end
inputs.each do |k, v|
subject.instance_variable_set("@#{k}", v)
end
subject.finalize!
expect(subject.proxy_command).to eq(proxy_command)
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