diff --git a/plugins/providers/virtualbox/action/network.rb b/plugins/providers/virtualbox/action/network.rb index ece5ce03a..6793808d3 100644 --- a/plugins/providers/virtualbox/action/network.rb +++ b/plugins/providers/virtualbox/action/network.rb @@ -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 diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index dea145ade..89a97b5a2 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -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 diff --git a/plugins/providers/virtualbox/driver/version_7_0.rb b/plugins/providers/virtualbox/driver/version_7_0.rb index d94e66b93..efa820b62 100644 --- a/plugins/providers/virtualbox/driver/version_7_0.rb +++ b/plugins/providers/virtualbox/driver/version_7_0.rb @@ -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+\{(?.+?)\}$/) + 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 diff --git a/test/unit/plugins/providers/virtualbox/action/network_test.rb b/test/unit/plugins/providers/virtualbox/action/network_test.rb index 2e01fa527..b74c46b79 100644 --- a/test/unit/plugins/providers/virtualbox/action/network_test.rb +++ b/test/unit/plugins/providers/virtualbox/action/network_test.rb @@ -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", diff --git a/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb index 58c9e6dac..2f2d3695e 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_7_0_test.rb @@ -26,31 +26,7 @@ LogFldr="/VirtualBox VMs/vagrant-test_default_1665781960041_56631/Logs" memory=1024) } let(:config_file) { - StringIO.new( -%( - - - - - - - - - - - - - - - - - - - - - -) - ) + 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=%( + + + + + + + + + + + + + + + + + + + + + +) + + +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)