Updates the VirtualBox version check for network range validation
to access the driver via the machine instances provider within the
passed env.
446 lines
13 KiB
Ruby
446 lines
13 KiB
Ruby
require_relative "../base"
|
|
|
|
require "vagrant/util/platform"
|
|
|
|
describe VagrantPlugins::ProviderVirtualBox::Action::Network do
|
|
include_context "unit"
|
|
include_context "virtualbox"
|
|
|
|
let(:iso_env) do
|
|
# We have to create a Vagrantfile so there is a root path
|
|
env = isolated_environment
|
|
env.vagrantfile("")
|
|
env.create_vagrant_env
|
|
end
|
|
|
|
let(:machine) do
|
|
iso_env.machine(iso_env.machine_names[0], :virtualbox).tap do |m|
|
|
allow(m.provider).to receive(:driver).and_return(driver)
|
|
end
|
|
end
|
|
|
|
let(:env) {{ machine: machine, ui: machine.ui }}
|
|
let(:app) { lambda { |*args| }}
|
|
let(:driver) { double("driver", version: vbox_version) }
|
|
let(:vbox_version) { "6.1.0" }
|
|
|
|
let(:nics) { {} }
|
|
|
|
subject { described_class.new(app, env) }
|
|
|
|
before do
|
|
allow(driver).to receive(:enable_adapters)
|
|
allow(driver).to receive(:read_network_interfaces) { nics }
|
|
end
|
|
|
|
describe "#hostonly_config" do
|
|
before do
|
|
allow(subject).to receive(:hostonly_find_matching_network)
|
|
allow(driver).to receive(:read_bridged_interfaces).and_return([])
|
|
subject.instance_eval do
|
|
def env=(e)
|
|
@env = e
|
|
end
|
|
end
|
|
|
|
subject.env = env
|
|
end
|
|
|
|
let(:options) {
|
|
{
|
|
type: type,
|
|
ip: address,
|
|
}
|
|
}
|
|
let(:type) { :dhcp }
|
|
let(:address) { nil }
|
|
|
|
it "should validate the IP" do
|
|
expect(subject).to receive(:validate_hostonly_ip!)
|
|
subject.hostonly_config(options)
|
|
end
|
|
end
|
|
|
|
describe "#validate_hostonly_ip!" do
|
|
let(:address) { "192.168.1.2" }
|
|
let(:net_conf) { [IPAddr.new(address + "/24")]}
|
|
let(:vbox_version) { "6.1.28" }
|
|
|
|
before do
|
|
allow(subject).to receive(:load_net_conf).and_return(net_conf)
|
|
expect(subject).to receive(:validate_hostonly_ip!).and_call_original
|
|
end
|
|
|
|
it "should load net configuration" do
|
|
expect(subject).to receive(:load_net_conf).and_return(net_conf)
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
end
|
|
|
|
context "when address is within ranges" do
|
|
it "should not error" do
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
end
|
|
end
|
|
|
|
context "when address is not found within ranges" do
|
|
let(:net_conf) { [IPAddr.new("127.0.0.1/20")] }
|
|
|
|
it "should raise an error" do
|
|
expect {
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
}.to raise_error(Vagrant::Errors::VirtualBoxInvalidHostSubnet)
|
|
end
|
|
end
|
|
|
|
context "when virtualbox version does not restrict range" do
|
|
let(:vbox_version) { "6.1.20" }
|
|
|
|
it "should not error" do
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
end
|
|
|
|
it "should not attempt to load network configuration" do
|
|
expect(subject).not_to receive(:load_net_conf)
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
end
|
|
end
|
|
|
|
context "when platform is windows" do
|
|
before do
|
|
allow(Vagrant::Util::Platform).to receive(:windows?).and_return(true)
|
|
end
|
|
|
|
it "should not error" do
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
end
|
|
|
|
it "should not attempt to load network configuration" do
|
|
expect(subject).not_to receive(:load_net_conf)
|
|
subject.validate_hostonly_ip!(address, driver)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#load_net_conf" do
|
|
let(:file_contents) { [""] }
|
|
|
|
before do
|
|
allow(File).to receive(:exist?).and_call_original
|
|
allow(File).to receive(:exist?).
|
|
with(described_class.const_get(:VBOX_NET_CONF)).
|
|
and_return(true)
|
|
allow(File).to receive(:readlines).
|
|
with(described_class.const_get(:VBOX_NET_CONF)).
|
|
and_return(file_contents)
|
|
end
|
|
|
|
it "should read the configuration file" do
|
|
expect(File).to receive(:readlines).
|
|
with(described_class.const_get(:VBOX_NET_CONF)).
|
|
and_return(file_contents)
|
|
|
|
subject.load_net_conf
|
|
end
|
|
|
|
context "when file has comments only" do
|
|
let(:file_contents) {
|
|
[
|
|
"# A comment",
|
|
"# Another comment",
|
|
]
|
|
}
|
|
|
|
it "should return an empty array" do
|
|
expect(subject.load_net_conf).to eq([])
|
|
end
|
|
end
|
|
|
|
context "when file has valid range entries" do
|
|
let(:file_contents) {
|
|
[
|
|
"* 127.0.0.1/24",
|
|
"* 192.168.1.1/24",
|
|
]
|
|
}
|
|
|
|
it "should return an array with content" do
|
|
expect(subject.load_net_conf).not_to be_empty
|
|
end
|
|
|
|
it "should include IPAddr instances" do
|
|
subject.load_net_conf.each do |entry|
|
|
expect(entry).to be_a(IPAddr)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when file has valid range entries and comments" do
|
|
let(:file_contents) {
|
|
[
|
|
"# Comment in file",
|
|
"* 127.0.0.0/8",
|
|
"random text",
|
|
" * 192.168.2.0/28",
|
|
]
|
|
}
|
|
|
|
it "should contain two entries" do
|
|
expect(subject.load_net_conf.size).to eq(2)
|
|
end
|
|
end
|
|
|
|
context "when file has multiple entries on single line" do
|
|
let(:file_contents) {
|
|
[
|
|
"* 0.0.0.0/0 ::/0"
|
|
]
|
|
}
|
|
|
|
it "should contain two entries" do
|
|
expect(subject.load_net_conf.size).to eq(2)
|
|
end
|
|
|
|
it "should contain an ipv4 and ipv6 range" do
|
|
result = subject.load_net_conf
|
|
expect(result.first).to be_ipv4
|
|
expect(result.last).to be_ipv6
|
|
end
|
|
end
|
|
end
|
|
|
|
it "calls the next action in the chain" do
|
|
called = false
|
|
app = lambda { |*args| called = true }
|
|
|
|
action = described_class.new(app, env)
|
|
action.call(env)
|
|
|
|
expect(called).to eq(true)
|
|
end
|
|
|
|
it "creates a host-only interface with an IPv6 address <prefix>:1" do
|
|
guest = double("guest")
|
|
machine.config.vm.network 'private_network', type: :static, ip: 'dead:beef::100'
|
|
#allow(driver).to receive(:read_bridged_interfaces) { [] }
|
|
allow(driver).to receive(:read_host_only_interfaces) { [] }
|
|
#allow(driver).to receive(:read_dhcp_servers) { [] }
|
|
allow(machine).to receive(:guest) { guest }
|
|
allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}
|
|
allow(guest).to receive(:capability)
|
|
interface_ip = 'dead:beef::1'
|
|
|
|
subject.call(env)
|
|
|
|
expect(driver).to have_received(:create_host_only_network).with({
|
|
adapter_ip: interface_ip,
|
|
netmask: 64,
|
|
})
|
|
|
|
expect(guest).to have_received(:capability).with(:configure_networks, [{
|
|
type: :static6,
|
|
adapter_ip: 'dead:beef::1',
|
|
ip: 'dead:beef::100',
|
|
netmask: 64,
|
|
auto_config: true,
|
|
interface: nil
|
|
}])
|
|
end
|
|
|
|
it "raises the appropriate error when provided with an invalid IP address" do
|
|
machine.config.vm.network 'private_network', ip: '192.168.33.06'
|
|
|
|
expect{ subject.call(env) }.to raise_error(Vagrant::Errors::NetworkAddressInvalid)
|
|
end
|
|
|
|
context "with a dhcp private network" do
|
|
let(:bridgedifs) { [] }
|
|
let(:hostonlyifs) { [] }
|
|
let(:dhcpservers) { [] }
|
|
let(:guest) { double("guest") }
|
|
let(:network_args) {{ type: :dhcp }}
|
|
|
|
before do
|
|
machine.config.vm.network 'private_network', **network_args
|
|
allow(driver).to receive(:read_bridged_interfaces) { bridgedifs }
|
|
allow(driver).to receive(:read_host_only_interfaces) { hostonlyifs }
|
|
allow(driver).to receive(:read_dhcp_servers) { dhcpservers }
|
|
allow(machine).to receive(:guest) { guest }
|
|
end
|
|
|
|
it "tries to setup dhpc server using the ip for the specified network" do
|
|
allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}
|
|
allow(driver).to receive(:create_dhcp_server)
|
|
allow(guest).to receive(:capability)
|
|
allow(subject).to receive(:hostonly_find_matching_network).and_return({name: "vboxnet1", ip: "192.168.55.1"})
|
|
|
|
subject.call(env)
|
|
|
|
expect(driver).to have_received(:create_dhcp_server).with('vboxnet1', {
|
|
adapter_ip: "192.168.55.1",
|
|
auto_config: true,
|
|
ip: "192.168.55.1",
|
|
mac: nil,
|
|
name: nil,
|
|
netmask: "255.255.255.0",
|
|
nic_type: nil,
|
|
type: :dhcp,
|
|
dhcp_ip: "192.168.55.2",
|
|
dhcp_lower: "192.168.55.3",
|
|
dhcp_upper: "192.168.55.254",
|
|
adapter: 2
|
|
})
|
|
|
|
expect(guest).to have_received(:capability).with(:configure_networks, [{
|
|
type: :dhcp,
|
|
adapter_ip: "192.168.55.1",
|
|
ip: "192.168.55.1",
|
|
netmask: "255.255.255.0",
|
|
auto_config: true,
|
|
interface: nil
|
|
}])
|
|
end
|
|
|
|
it "creates a host only interface and a dhcp server using default ips, then tells the guest to configure the network after boot" do
|
|
allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}
|
|
allow(driver).to receive(:create_dhcp_server)
|
|
allow(guest).to receive(:capability)
|
|
allow(subject).to receive(:hostonly_find_matching_network).and_return(nil)
|
|
|
|
subject.call(env)
|
|
|
|
expect(driver).to have_received(:create_host_only_network).with({
|
|
adapter_ip: '192.168.56.1',
|
|
netmask: '255.255.255.0',
|
|
})
|
|
|
|
expect(driver).to have_received(:create_dhcp_server).with('vboxnet0', {
|
|
adapter_ip: "192.168.56.1",
|
|
auto_config: true,
|
|
ip: "192.168.56.1",
|
|
mac: nil,
|
|
name: nil,
|
|
netmask: "255.255.255.0",
|
|
nic_type: nil,
|
|
type: :dhcp,
|
|
dhcp_ip: "192.168.56.2",
|
|
dhcp_lower: "192.168.56.3",
|
|
dhcp_upper: "192.168.56.254",
|
|
adapter: 2
|
|
})
|
|
|
|
expect(guest).to have_received(:capability).with(:configure_networks, [{
|
|
type: :dhcp,
|
|
adapter_ip: "192.168.56.1",
|
|
ip: "192.168.56.1",
|
|
netmask: "255.255.255.0",
|
|
auto_config: true,
|
|
interface: nil
|
|
}])
|
|
end
|
|
|
|
context "when the default vbox dhcpserver is present from a fresh vbox install (see issue #3803)" do
|
|
let(:dhcpservers) {[
|
|
{
|
|
network_name: 'HostInterfaceNetworking-vboxnet0',
|
|
network: 'vboxnet0',
|
|
ip: '192.168.56.100',
|
|
netmask: '255.255.255.0',
|
|
lower: '192.168.56.101',
|
|
upper: '192.168.56.254'
|
|
}
|
|
]}
|
|
|
|
it "removes the invalid dhcpserver so it won't collide with any host only interface" do
|
|
allow(driver).to receive(:remove_dhcp_server)
|
|
allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}
|
|
allow(driver).to receive(:create_dhcp_server)
|
|
allow(guest).to receive(:capability)
|
|
|
|
subject.call(env)
|
|
|
|
expect(driver).to have_received(:remove_dhcp_server).with('HostInterfaceNetworking-vboxnet0')
|
|
end
|
|
|
|
context "but the user has intentionally configured their network just that way" do
|
|
let (:network_args) {{
|
|
type: :dhcp,
|
|
adapter_ip: '192.168.56.1',
|
|
dhcp_ip: '192.168.56.100',
|
|
dhcp_lower: '192.168.56.101',
|
|
dhcp_upper: '192.168.56.254'
|
|
}}
|
|
|
|
it "does not attempt to remove the dhcpserver" do
|
|
allow(driver).to receive(:remove_dhcp_server)
|
|
allow(driver).to receive(:create_host_only_network) {{ name: 'vboxnet0' }}
|
|
allow(driver).to receive(:create_dhcp_server)
|
|
allow(guest).to receive(:capability)
|
|
|
|
subject.call(env)
|
|
|
|
expect(driver).not_to have_received(:remove_dhcp_server).with('HostInterfaceNetworking-vboxnet0')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with invalid settings' do
|
|
[
|
|
{ ip: 'foo'},
|
|
{ ip: '1.2.3'},
|
|
{ ip: 'dead::beef::'},
|
|
{ ip: '192.168.56.3', netmask: 64},
|
|
{ ip: '192.168.56.3', netmask: 'ffff:ffff::'},
|
|
{ ip: 'dead:beef::', netmask: 'foo:bar::'},
|
|
{ ip: 'dead:beef::', netmask: '255.255.255.0'}
|
|
].each do |args|
|
|
it 'raises an exception' do
|
|
machine.config.vm.network 'private_network', **args
|
|
expect { subject.call(env) }.
|
|
to raise_error(Vagrant::Errors::NetworkAddressInvalid)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#hostonly_find_matching_network" do
|
|
let(:ip){ "192.168.55.2" }
|
|
let(:config){ {ip: ip, netmask: "255.255.255.0"} }
|
|
let(:interfaces){ [] }
|
|
|
|
before do
|
|
allow(driver).to receive(:read_host_only_interfaces).and_return(interfaces)
|
|
subject.instance_variable_set(:@env, env)
|
|
end
|
|
|
|
context "with no defined host interfaces" do
|
|
it "should return nil" do
|
|
expect(subject.hostonly_find_matching_network(config)).to be_nil
|
|
end
|
|
end
|
|
|
|
context "with matching host interface" do
|
|
let(:interfaces){ [{ip: "192.168.55.1", netmask: "255.255.255.0", name: "vnet"}] }
|
|
|
|
it "should return matching interface" do
|
|
expect(subject.hostonly_find_matching_network(config)).to eq(interfaces.first)
|
|
end
|
|
|
|
context "with matching name" do
|
|
let(:config){ {ip: ip, netmask: "255.255.255.0", name: "vnet"} }
|
|
|
|
it "should return matching interface" do
|
|
expect(subject.hostonly_find_matching_network(config)).to eq(interfaces.first)
|
|
end
|
|
end
|
|
|
|
context "with non-matching name" do
|
|
let(:config){ {ip: ip, netmask: "255.255.255.0", name: "unknown"} }
|
|
|
|
it "should return nil" do
|
|
expect(subject.hostonly_find_matching_network(config)).to be_nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|