Merge pull request #12983 from chrisroberts/vbox-networking

Add hostonlynets support to VirtualBox driver
This commit is contained in:
Chris Roberts 2022-11-08 13:52:19 -08:00 committed by GitHub
commit da0ab49fb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 856 additions and 70 deletions

View File

@ -21,6 +21,8 @@ module VagrantPlugins
VBOX_NET_CONF = "/etc/vbox/networks.conf".freeze
# Version of VirtualBox that introduced hostonly network range restrictions
HOSTONLY_VALIDATE_VERSION = Gem::Version.new("6.1.28")
# Version of VirtualBox on darwin platform that ignores restrictions
DARWIN_IGNORE_HOSTONLY_VALIDATE_VERSION = Gem::Version.new("7.0.0")
# Default valid range for hostonly networks
HOSTONLY_DEFAULT_RANGE = [IPAddr.new("192.168.56.0/21").freeze].freeze
@ -479,10 +481,7 @@ module VagrantPlugins
#-----------------------------------------------------------------
# This creates a host only network for the given configuration.
def hostonly_create_network(config)
@env[:machine].provider.driver.create_host_only_network(
adapter_ip: config[:adapter_ip],
netmask: config[:netmask]
)
@env[:machine].provider.driver.create_host_only_network(config)
end
# This finds a matching host only network for the given configuration.
@ -517,7 +516,11 @@ module VagrantPlugins
# placed on the valid ranges
def validate_hostonly_ip!(ip, driver)
return if Gem::Version.new(driver.version) < HOSTONLY_VALIDATE_VERSION ||
Vagrant::Util::Platform.windows?
(
Vagrant::Util::Platform.darwin? &&
Gem::Version.new(driver.version) >= DARWIN_IGNORE_HOSTONLY_VALIDATE_VERSION
) ||
Vagrant::Util::Platform.windows?
ip = IPAddr.new(ip.to_s) if !ip.is_a?(IPAddr)
valid_ranges = load_net_conf

View File

@ -104,15 +104,15 @@ module VagrantPlugins
end
end
# Creates a disk. Default format is VDI unless overridden
#
# @param [String] disk_file
# @param [Integer] disk_size - size in bytes
# @param [String] disk_format - format of disk, defaults to "VDI"
# Creates a disk. Default format is VDI unless overridden
#
# @param [String] disk_file
# @param [Integer] disk_size - size in bytes
# @param [String] disk_format - format of disk, defaults to "VDI"
# @param [Hash] opts - additional options
def create_disk(disk_file, disk_size, disk_format="VDI", **opts)
execute("createmedium", '--filename', disk_file, '--sizebyte', disk_size.to_i.to_s, '--format', disk_format)
end
def create_disk(disk_file, disk_size, disk_format="VDI", **opts)
execute("createmedium", '--filename', disk_file, '--sizebyte', disk_size.to_i.to_s, '--format', disk_format)
end
def create_host_only_network(options)
@ -322,22 +322,22 @@ module VagrantPlugins
if adapter[:bridge]
args.concat(["--bridgeadapter#{adapter[:adapter]}",
adapter[:bridge], "--cableconnected#{adapter[:adapter]}", "on"])
adapter[:bridge], "--cableconnected#{adapter[:adapter]}", "on"])
end
if adapter[:hostonly]
args.concat(["--hostonlyadapter#{adapter[:adapter]}",
adapter[:hostonly], "--cableconnected#{adapter[:adapter]}", "on"])
adapter[:hostonly], "--cableconnected#{adapter[:adapter]}", "on"])
end
if adapter[:intnet]
args.concat(["--intnet#{adapter[:adapter]}",
adapter[:intnet], "--cableconnected#{adapter[:adapter]}", "on"])
adapter[:intnet], "--cableconnected#{adapter[:adapter]}", "on"])
end
if adapter[:mac_address]
args.concat(["--macaddress#{adapter[:adapter]}",
adapter[:mac_address]])
adapter[:mac_address]])
end
if adapter[:nic_type]
@ -361,7 +361,7 @@ module VagrantPlugins
# If the file already exists we'll throw a custom error
raise Vagrant::Errors::VirtualBoxFileExists,
stderr: e.extra_data[:stderr]
stderr: e.extra_data[:stderr]
end
end
end
@ -370,14 +370,14 @@ module VagrantPlugins
args = []
ports.each do |options|
pf_builder = [options[:name],
options[:protocol] || "tcp",
options[:hostip] || "",
options[:hostport],
options[:guestip] || "",
options[:guestport]]
options[:protocol] || "tcp",
options[:hostip] || "",
options[:hostport],
options[:guestip] || "",
options[:guestport]]
args.concat(["--natpf#{options[:adapter] || 1}",
pf_builder.join(",")])
pf_builder.join(",")])
end
execute("modifyvm", @uuid, *args, retryable: true) if !args.empty?
@ -507,11 +507,11 @@ module VagrantPlugins
# since this comes first.
current_nic = $1.to_i if line =~ /^nic(\d+)=".+?"$/
# If we care about active VMs only, then we check the state
# to verify the VM is running.
if active_only && line =~ /^VMState="(.+?)"$/ && $1.to_s != "running"
return []
end
# If we care about active VMs only, then we check the state
# to verify the VM is running.
if active_only && line =~ /^VMState="(.+?)"$/ && $1.to_s != "running"
return []
end
# Parse out the forwarded port information
# Forwarding(1)="172.22.8.201tcp32977,tcp,172.22.8.201,32977,,3777"
@ -600,7 +600,7 @@ module VagrantPlugins
if !valid_ip_address?(ip)
raise Vagrant::Errors::VirtualBoxGuestPropertyNotFound,
guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP"
guest_property: "/VirtualBox/GuestInfo/Net/#{adapter_number}/V4/IP"
end
return ip
@ -771,7 +771,7 @@ module VagrantPlugins
# We got VERR_ALREADY_EXISTS. This means that we're renaming to
# a VM name that already exists. Raise a custom error.
raise Vagrant::Errors::VirtualBoxNameExists,
stderr: e.extra_data[:stderr]
stderr: e.extra_data[:stderr]
end
end
end
@ -791,9 +791,9 @@ module VagrantPlugins
hostpath = Vagrant::Util::Platform.windows_path(folder[:hostpath])
end
args = ["--name",
folder[:name],
"--hostpath",
hostpath]
folder[:name],
"--hostpath",
hostpath]
args << "--transient" if folder.key?(:transient) && folder[:transient]
args << "--automount" if folder.key?(:automount) && folder[:automount]
@ -866,8 +866,8 @@ module VagrantPlugins
# If we reached this point then it didn't work out.
raise Vagrant::Errors::VBoxManageError,
command: command.inspect,
stderr: r.stderr
command: command.inspect,
stderr: r.stderr
end
end

View File

@ -6,12 +6,191 @@ module VagrantPlugins
module Driver
# Driver for VirtualBox 7.0.x
class Version_7_0 < Version_6_1
# VirtualBox version requirement for using host only networks
# instead of host only interfaces
HOSTONLY_NET_REQUIREMENT=Gem::Requirement.new(">= 7")
# Prefix of name used for host only networks
HOSTONLY_NAME_PREFIX="vagrantnet-vbox"
DEFAULT_NETMASK="255.255.255.0"
def initialize(uuid)
super
@logger = Log4r::Logger.new("vagrant::provider::virtualbox_7_0")
end
def read_bridged_interfaces
ifaces = super
return ifaces if !use_host_only_nets?
# Get a list of all subnets which are in use for hostonly networks
hostonly_ifaces = read_host_only_networks.map do |net|
IPAddr.new(net[:lowerip]).mask(net[:networkmask])
end
# Prune any hostonly interfaces in the list
ifaces.delete_if { |i|
addr = IPAddr.new(i[:ip]).mask(i[:netmask])
hostonly_ifaces.include?(addr)
}
ifaces
end
def delete_unused_host_only_networks
return super if !use_host_only_nets?
# First get the list of existing host only network names
network_names = read_host_only_networks.map { |net| net[:name] }
# Prune the network names to only include ones we manage
network_names.delete_if { |name| !name.start_with?(HOSTONLY_NAME_PREFIX) }
@logger.debug("managed host only network names: #{network_names}")
return if network_names.empty?
# Next get the list of host only networks currently in use
inuse_names = []
execute("list", "vms", retryable: true).split("\n").each do |line|
match = line.match(/^".+?"\s+\{(?<vmid>.+?)\}$/)
next if match.nil?
begin
info = execute("showvminfo", match[:vmid].to_s, "--machinereadable", retryable: true)
info.split("\n").each do |vmline|
if vmline.start_with?("hostonly-network")
net_name = vmline.split("=", 2).last.to_s.gsub('"', "")
inuse_names << net_name
end
end
rescue Vagrant::Errors::VBoxManageError => err
raise if !err.extra_data[:stderr].include?("VBOX_E_OBJECT_NOT_FOUND")
end
end
@logger.debug("currently in use network names: #{inuse_names}")
# Now remove all the networks not in use
(network_names - inuse_names).each do |name|
execute("hostonlynet", "remove", "--name", name, retryable: true)
end
end
def enable_adapters(adapters)
return super if !use_host_only_nets?
hostonly_adapters = adapters.find_all { |adapter| adapter[:hostonly] }
other_adapters = adapters - hostonly_adapters
super(other_adapters) if !other_adapters.empty?
if !hostonly_adapters.empty?
args = []
hostonly_adapters.each do |adapter|
args.concat(["--nic#{adapter[:adapter]}", "hostonlynet"])
args.concat(["--host-only-net#{adapter[:adapter]}", adapter[:hostonly],
"--cableconnected#{adapter[:adapter]}", "on"])
end
execute("modifyvm", @uuid, *args, retryable: true)
end
end
def create_host_only_network(options)
# If we are not on macOS, just setup the hostonly interface
return super if !use_host_only_nets?
opts = {
netmask: options.fetch(:netmask, DEFAULT_NETMASK),
}
if options[:type] == :dhcp
opts[:lower] = options[:dhcp_lower]
opts[:upper] = options[:dhcp_upper]
else
addr = IPAddr.new(options[:adapter_ip])
opts[:upper] = opts[:lower] = addr.mask(opts[:netmask]).to_range.first.to_s
end
name_idx = read_host_only_networks.map { |hn|
next if !hn[:name].start_with?(HOSTONLY_NAME_PREFIX)
hn[:name].sub(HOSTONLY_NAME_PREFIX, "").to_i
}.compact.max.to_i + 1
opts[:name] = HOSTONLY_NAME_PREFIX + name_idx.to_s
execute("hostonlynet", "add",
"--name", opts[:name],
"--netmask", opts[:netmask],
"--lower-ip", opts[:lower],
"--upper-ip", opts[:upper],
retryable: true)
{
name: opts[:name],
ip: options[:adapter_ip],
netmask: opts[:netmask],
}
end
# Disabled when host only nets are in use
def reconfig_host_only(options)
return super if !use_host_only_nets?
end
# Disabled when host only nets are in use since
# the host only nets will provide the dhcp server
def remove_dhcp_server(*_, **_)
super if !use_host_only_nets?
end
# Disabled when host only nets are in use since
# the host only nets will provide the dhcp server
def create_dhcp_server(*_, **_)
super if !use_host_only_nets?
end
def read_host_only_interfaces
return super if !use_host_only_nets?
# When host only nets are in use, read them and
# reformat the information to line up with how
# the interfaces is structured
read_host_only_networks.map do |net|
addr = IPAddr.new(net[:lowerip])
net[:netmask] = net[:networkmask]
if addr.ipv4?
net[:ip] = addr.mask(net[:netmask]).to_s
net[:ipv6] = ""
else
net[:ip] = ""
net[:ipv6] = addr.mask(net[:netmwask]).to_s
net[:ipv6_prefix] = net[:netmask]
end
net[:status] = net[:state] == "Enabled" ? "Up" : "Down"
net
end
end
# Generate list of host only networks
def read_host_only_networks
networks = []
current = nil
execute("list", "hostonlynets", retryable: true).split("\n").each do |line|
line.chomp!
next if line.empty?
key, value = line.split(":", 2).map(&:strip)
key = key.downcase
if key == "name"
networks.push(current) if !current.nil?
current = Vagrant::Util::HashWithIndifferentAccess.new
end
current[key] = value
end
networks.push(current) if !current.nil?
networks
end
# The initial VirtualBox 7.0 release has an issue with displaying port
# forward information. When a single port forward is defined, the forwarding
# information can be found in the `showvminfo` output. Once more than a
@ -20,11 +199,8 @@ module VagrantPlugins
# file from the `showvminfo` output and extract the port forward information
# from there instead.
def read_forwarded_ports(uuid=nil, active_only=false)
@version ||= Meta.new.version
# Only use this override for the 7.0.0 release. If it is still broken
# on the 7.0.1 release we can modify the version check.
return super if @version != "7.0.0"
# Only use this override for the 7.0.0 release.
return super if get_version.to_s != "7.0.0"
uuid ||= @uuid
@ -61,6 +237,25 @@ module VagrantPlugins
results
end
private
# Returns if hostonlynets are enabled on the current
# host platform
#
# @return [Boolean]
def use_host_only_nets?
Vagrant::Util::Platform.darwin? &&
HOSTONLY_NET_REQUIREMENT.satisfied_by?(get_version)
end
# VirtualBox version in use
#
# @return [Gem::Version]
def get_version
return @version if @version
@version = Gem::Version.new(Meta.new.version)
end
end
end
end

View File

@ -231,10 +231,10 @@ describe VagrantPlugins::ProviderVirtualBox::Action::Network do
subject.call(env)
expect(driver).to have_received(:create_host_only_network).with({
expect(driver).to have_received(:create_host_only_network).with(hash_including({
adapter_ip: interface_ip,
netmask: 64,
})
}))
expect(guest).to have_received(:capability).with(:configure_networks, [{
type: :static6,
@ -308,10 +308,10 @@ describe VagrantPlugins::ProviderVirtualBox::Action::Network do
subject.call(env)
expect(driver).to have_received(:create_host_only_network).with({
expect(driver).to have_received(:create_host_only_network).with(hash_including({
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",

View File

@ -26,31 +26,7 @@ LogFldr="/VirtualBox VMs/vagrant-test_default_1665781960041_56631/Logs"
memory=1024)
}
let(:config_file) {
StringIO.new(
%(<?xml version="1.0"?>
<VirtualBox xmlns="http://www.virtualbox.org/" version="1.19-linux">
<Machine uuid="{623842dc-0947-4143-aa4e-7d180c5eb348}" name="vagrant-test_default_1665781960041_56631" OSType="Ubuntu_64" snapshotFolder="Snapshots">
<Hardware>
<Network>
<Adapter slot="0" enabled="true" MACAddress="080027BB1475" type="82540EM">
<NAT localhost-reachable="true">
<DNS use-proxy="true"/>
<Forwarding name="ssh" proto="1" hostip="127.0.0.1" hostport="2222" guestport="22"/>
<Forwarding name="tcp7700" proto="1" hostport="7700" guestport="80"/>
</NAT>
</Adapter>
<Adapter slot="1" enabled="true" MACAddress="080027DD5ADF" type="82540EM">
<DisabledModes>
<InternalNetwork name="intnet"/>
<NATNetwork name="NatNetwork"/>
</DisabledModes>
<HostOnlyInterface name="vboxnet0"/>
</Adapter>
</Network>
</Hardware>
</Machine>
</VirtualBox>)
)
StringIO.new(VBOX_VMCONFIG_FILE)
}
before do
@ -106,4 +82,616 @@ memory=1024)
end
end
describe "#use_host_only_nets?" do
context "when platform is darwin" do
before do
allow(Vagrant::Util::Platform).to receive(:darwin?).and_return(true)
end
context "when virtualbox version is less than 7" do
before do
allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).
to receive(:version).and_return("6.0.28")
end
it "should return false" do
expect(subject.send(:use_host_only_nets?)).to be(false)
end
end
context "when virtualbox version is greater than 7" do
before do
allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).
to receive(:version).and_return("7.0.2")
end
it "should return true" do
expect(subject.send(:use_host_only_nets?)).to be(true)
end
end
context "when virtualbox version is equal to 7" do
before do
allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).
to receive(:version).and_return("7.0.0")
end
it "should return true" do
expect(subject.send(:use_host_only_nets?)).to be(true)
end
end
end
context "when platform is not darwin" do
before do
allow(Vagrant::Util::Platform).to receive(:darwin?).and_return(false)
end
context "when virtualbox version is less than 7" do
before do
allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).
to receive(:version).and_return("6.0.28")
end
it "should return false" do
expect(subject.send(:use_host_only_nets?)).to be(false)
end
end
context "when virtualbox version is greater than 7" do
before do
allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).
to receive(:version).and_return("7.0.2")
end
it "should return false" do
expect(subject.send(:use_host_only_nets?)).to be(false)
end
end
context "when virtualbox version is equal to 7" do
before do
allow_any_instance_of(VagrantPlugins::ProviderVirtualBox::Driver::Meta).
to receive(:version).and_return("7.0.0")
end
it "should return false" do
expect(subject.send(:use_host_only_nets?)).to be(false)
end
end
end
end
describe "#read_bridged_interfaces" do
before do
allow(subject).to receive(:execute).and_call_original
expect(subject).
to receive(:execute).
with("list", "bridgedifs").
and_return(VBOX_BRIDGEDIFS)
end
context "when hostonlynets are not enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should return all interfaces in list" do
expect(subject.read_bridged_interfaces.size).to eq(5)
end
it "should not read host only networks" do
expect(subject).not_to receive(:read_host_only_networks)
subject.read_bridged_interfaces
end
end
context "when hostonlynets are enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
end
it "should return all interfaces in list" do
expect(subject).to receive(:read_host_only_networks).and_return([])
expect(subject.read_bridged_interfaces.size).to eq(5)
end
context "when hostonly networks are defined" do
before do
expect(subject).
to receive(:execute).
with("list", "hostonlynets", any_args).
and_return(VBOX_HOSTONLYNETS)
end
it "should not return all interfaces in list" do
expect(subject.read_bridged_interfaces.size).to_not eq(5)
end
it "should not include hostonly network devices" do
expect(
subject.read_bridged_interfaces.any? { |int|
int[:name].start_with?("bridge")
}
).to be(false)
end
end
end
end
describe "#delete_unused_host_only_networks" do
context "when hostonlynets are not enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should remove host only interfaces" do
expect(subject).to receive(:execute).with("list", "hostonlyifs", any_args).and_return("")
expect(subject).to receive(:execute).with("list", "vms", any_args).and_return("")
subject.delete_unused_host_only_networks
end
it "should not read host only networks" do
expect(subject).to receive(:execute).with("list", "hostonlyifs", any_args).and_return("")
expect(subject).to receive(:execute).with("list", "vms", any_args).and_return("")
expect(subject).not_to receive(:read_host_only_networks)
subject.delete_unused_host_only_networks
end
end
context "when hostonlynets are enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
allow(subject).to receive(:read_host_only_networks).and_return([])
allow(subject).to receive(:execute).with("list", "vms", any_args).and_return("")
end
it "should not read host only interfaces" do
expect(subject).not_to receive(:execute).with("list", "hostonlyifs", any_args)
subject.delete_unused_host_only_networks
end
context "when no host only networks are defined" do
before do
expect(subject).to receive(:read_host_only_networks).and_return([])
end
it "should not list vms" do
expect(subject).not_to receive(:execute).with("list", "vms", any_args)
subject.delete_unused_host_only_networks
end
end
context "when host only networks are defined" do
before do
expect(subject).
to receive(:read_host_only_networks).
and_return([{name: "vagrantnet-vbox-1"}])
end
context "when no vms are using the network" do
before do
expect(subject).to receive(:execute).with("list", "vms", any_args).and_return("")
end
it "should delete the network" do
expect(subject).
to receive(:execute).
with("hostonlynet", "remove", "--name", "vagrantnet-vbox-1", any_args)
subject.delete_unused_host_only_networks
end
end
context "when vms are using the network" do
before do
expect(subject).
to receive(:execute).
with("list", "vms", any_args).
and_return(%("VM_NAME" {VM_ID}))
expect(subject).
to receive(:execute).
with("showvminfo", "VM_ID", any_args).
and_return(%(hostonly-network="vagrantnet-vbox-1"))
end
it "should not delete the network" do
expect(subject).not_to receive(:execute).with("hostonlynet", "remove", any_args)
subject.delete_unused_host_only_networks
end
end
end
end
end
describe "#enable_adapters" do
let(:adapters) {
[{hostonly: "hostonlynetwork", adapter: 1},
{bridge: "eth0", adapter: 2}]
}
before do
allow(subject).to receive(:execute).with("modifyvm", any_args)
end
context "when hostonlynets are not enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should only call modifyvm once" do
expect(subject).to receive(:execute).with("modifyvm", any_args).once
subject.enable_adapters(adapters)
end
it "should configure host only network using hostonlyadapter" do
expect(subject).to receive(:execute) { |*args|
expect(args.first).to eq("modifyvm")
expect(args).to include("--hostonlyadapter1")
true
}
subject.enable_adapters(adapters)
end
end
context "when hostonlynets are enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
end
it "should call modifyvm twice" do
expect(subject).to receive(:execute).with("modifyvm", any_args).twice
subject.enable_adapters(adapters)
end
it "should configure host only network using hostonlynet" do
expect(subject).to receive(:execute).once
expect(subject).to receive(:execute) { |*args|
expect(args.first).to eq("modifyvm")
expect(args).to include("--host-only-net1")
true
}
subject.enable_adapters(adapters)
end
end
end
describe "#create_host_only_network" do
let(:options) {
{
adapter_ip: "127.0.0.1",
netmask: "255.255.255.0"
}
}
context "when hostonlynets are disabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should create using hostonlyif" do
expect(subject).
to receive(:execute).
with("hostonlyif", "create", any_args).
and_return("Interface 'host_only' was successfully created")
expect(subject).
to receive(:execute).
with("hostonlyif", "ipconfig", "host_only", any_args)
subject.create_host_only_network(options)
end
end
context "when hostonlynets are enabled" do
let(:prefix) { described_class.const_get(:HOSTONLY_NAME_PREFIX) }
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
allow(subject).to receive(:read_host_only_networks).and_return([])
end
it "should create using hostonlynet" do
expect(subject).
to receive(:execute).
with("hostonlynet", "add", "--name", prefix + "1",
"--netmask", options[:netmask], "--lower-ip",
"127.0.0.0", "--upper-ip", "127.0.0.0", any_args)
subject.create_host_only_network(options)
end
context "when other host only networks exist" do
before do
expect(subject).
to receive(:read_host_only_networks).
and_return(["custom", prefix + "1", prefix + "20"].map { |n| {name: n} })
end
it "should create network with incremented name" do
expect(subject).
to receive(:execute).
with("hostonlynet", "add", "--name", prefix + "21", any_args)
subject.create_host_only_network(options)
end
end
context "when dhcp information is included" do
let(:options) {
{
type: :dhcp,
dhcp_lower: "127.0.0.1",
dhcp_upper: "127.0.1.200",
netmask: "255.255.240.0"
}
}
it "should set DHCP range" do
expect(subject).
to receive(:execute).
with("hostonlynet", "add", "--name", anything, "--netmask", options[:netmask],
"--lower-ip", options[:dhcp_lower], "--upper-ip", options[:dhcp_upper],
any_args)
subject.create_host_only_network(options)
end
end
end
end
describe "#reconfig_host_only" do
let(:interface) { {name: "iname", ipv6: "VALUE"} }
context "when hostonlynets are disabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should apply ipv6 update" do
expect(subject).to receive(:execute).with("hostonlyif", "ipconfig", interface[:name],
"--ipv6", interface[:ipv6], any_args)
subject.reconfig_host_only(interface)
end
end
context "when hostonlynets are enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
end
it "should do nothing" do
expect(subject).not_to receive(:execute)
subject.reconfig_host_only(interface)
end
end
end
describe "#remove_dhcp_server" do
let(:dhcp_name) { double(:dhcp_name) }
context "when hostonlynets are disabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should remove the dhcp server" do
expect(subject).to receive(:execute).with("dhcpserver", "remove", "--netname",
dhcp_name, any_args)
subject.remove_dhcp_server(dhcp_name)
end
end
context "when hostonlynets are enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
end
it "should do nothing" do
expect(subject).not_to receive(:execute)
subject.remove_dhcp_server(dhcp_name)
end
end
end
describe "#create_dhcp_server" do
let(:network) { double("network") }
let(:options) {
{
dhcp_ip: "127.0.0.1",
netmask: "255.255.255.0",
dhcp_lower: "127.0.0.2",
dhcp_upper: "127.0.0.200"
}
}
context "when hostonlynets is diabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
end
it "should create a dhcp server" do
expect(subject).to receive(:execute).with("dhcpserver", "add", "--ifname", network,
"--ip", options[:dhcp_ip], any_args)
subject.create_dhcp_server(network, options)
end
end
context "when hostonlynets is enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
end
it "should do nothing" do
expect(subject).not_to receive(:execute)
subject.create_dhcp_server(network, options)
end
end
end
describe "#read_host_only_interfaces" do
context "when hostonlynets is diabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(false)
allow(subject).to receive(:execute).and_return("")
end
it "should list hostonlyifs" do
expect(subject).to receive(:execute).with("list", "hostonlyifs", any_args).and_return("")
subject.read_host_only_interfaces
end
it "should not call read_host_only_networks" do
expect(subject).not_to receive(:read_host_only_networks)
subject.read_host_only_interfaces
end
end
context "when hostonlynets is enabled" do
before do
allow(subject).to receive(:use_host_only_nets?).and_return(true)
allow(subject).to receive(:execute).with("list", "hostonlynets", any_args).
and_return(VBOX_HOSTONLYNETS)
end
it "should call read_host_only_networks" do
expect(subject).to receive(:read_host_only_networks).and_return([])
subject.read_host_only_interfaces
end
it "should return defined networks" do
expect(subject.read_host_only_interfaces.size).to eq(2)
end
it "should add compat information to network entries" do
result = subject.read_host_only_interfaces
expect(result.first[:netmask]).to eq(result.first[:networkmask])
expect(result.first[:status]).to eq("Up")
end
end
end
describe "#read_host_only_networks" do
before do
allow(subject).to receive(:execute).with("list", "hostonlynets", any_args).
and_return(VBOX_HOSTONLYNETS)
end
it "should return defined networks" do
expect(subject.read_host_only_networks.size).to eq(2)
end
it "should return expected network information" do
result = subject.read_host_only_networks
expect(result.first[:name]).to eq("vagrantnet-vbox1")
expect(result.first[:lowerip]).to eq("192.168.61.0")
expect(result.first[:networkmask]).to eq("255.255.255.0")
expect(result.last[:name]).to eq("vagrantnet-vbox2")
expect(result.last[:lowerip]).to eq("192.168.22.0")
expect(result.last[:networkmask]).to eq("255.255.255.0")
end
end
end
VBOX_VMCONFIG_FILE=%(<?xml version="1.0"?>
<VirtualBox xmlns="http://www.virtualbox.org/" version="1.19-linux">
<Machine uuid="{623842dc-0947-4143-aa4e-7d180c5eb348}" name="vagrant-test_default_1665781960041_56631" OSType="Ubuntu_64" snapshotFolder="Snapshots">
<Hardware>
<Network>
<Adapter slot="0" enabled="true" MACAddress="080027BB1475" type="82540EM">
<NAT localhost-reachable="true">
<DNS use-proxy="true"/>
<Forwarding name="ssh" proto="1" hostip="127.0.0.1" hostport="2222" guestport="22"/>
<Forwarding name="tcp7700" proto="1" hostport="7700" guestport="80"/>
</NAT>
</Adapter>
<Adapter slot="1" enabled="true" MACAddress="080027DD5ADF" type="82540EM">
<DisabledModes>
<InternalNetwork name="intnet"/>
<NATNetwork name="NatNetwork"/>
</DisabledModes>
<HostOnlyInterface name="vboxnet0"/>
</Adapter>
</Network>
</Hardware>
</Machine>
</VirtualBox>)
VBOX_BRIDGEDIFS=%(Name: en1: Wi-Fi (AirPort)
GUID: 00000000-0000-0000-0000-000000000001
DHCP: Disabled
IPAddress: 10.0.0.49
NetworkMask: 255.255.255.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: xx:xx:xx:xx:xx:01
MediumType: Ethernet
Wireless: Yes
Status: Up
VBoxNetworkName: HostInterfaceNetworking-en1
Name: en0: Ethernet
GUID: 00000000-0000-0000-0000-000000000002
DHCP: Disabled
IPAddress: 0.0.0.0
NetworkMask: 0.0.0.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: xx:xx:xx:xx:xx:02
MediumType: Ethernet
Wireless: No
Status: Up
VBoxNetworkName: HostInterfaceNetworking-en0
Name: bridge100
GUID: 00000000-0000-0000-0000-000000000003
DHCP: Disabled
IPAddress: 192.168.61.1
NetworkMask: 255.255.255.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: xx:xx:xx:xx:xx:03
MediumType: Ethernet
Wireless: No
Status: Up
VBoxNetworkName: HostInterfaceNetworking-bridge100
Name: en2: Thunderbolt 1
GUID: 00000000-0000-0000-0000-000000000004
DHCP: Disabled
IPAddress: 0.0.0.0
NetworkMask: 0.0.0.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: xx:xx:xx:xx:xx:04
MediumType: Ethernet
Wireless: No
Status: Up
VBoxNetworkName: HostInterfaceNetworking-en2
Name: bridge101
GUID: 00000000-0000-0000-0000-000000000005
DHCP: Disabled
IPAddress: 192.168.22.1
NetworkMask: 255.255.255.0
IPV6Address:
IPV6NetworkMaskPrefixLength: 0
HardwareAddress: xx:xx:xx:xx:xx:05
MediumType: Ethernet
Wireless: No
Status: Up
VBoxNetworkName: HostInterfaceNetworking-bridge101)
VBOX_HOSTONLYNETS=%(Name: vagrantnet-vbox1
GUID: 10000000-0000-0000-0000-000000000000
State: Enabled
NetworkMask: 255.255.255.0
LowerIP: 192.168.61.0
UpperIP: 192.168.61.0
VBoxNetworkName: hostonly-vagrantnet-vbox1
Name: vagrantnet-vbox2
GUID: 20000000-0000-0000-0000-000000000000
State: Enabled
NetworkMask: 255.255.255.0
LowerIP: 192.168.22.0
UpperIP: 192.168.22.0
VBoxNetworkName: hostonly-vagrantnet-vbox2)