From 85d97f538375a1f543da3ef1770e319bb89f3b06 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 24 Oct 2024 14:49:31 -0700 Subject: [PATCH] Add support for VirtualBox 7.1 --- plugins/providers/virtualbox/driver/meta.rb | 1 + .../virtualbox/driver/version_7_1.rb | 19 + plugins/providers/virtualbox/plugin.rb | 1 + .../unit/plugins/providers/virtualbox/base.rb | 1 + .../virtualbox/driver/version_7_0_test.rb | 770 +---------------- .../virtualbox/driver/version_7_1_test.rb | 15 + .../virtualbox_driver_version_7_x_examples.rb | 777 ++++++++++++++++++ .../docs/providers/virtualbox/index.mdx | 6 +- 8 files changed, 818 insertions(+), 772 deletions(-) create mode 100644 plugins/providers/virtualbox/driver/version_7_1.rb create mode 100644 test/unit/plugins/providers/virtualbox/driver/version_7_1_test.rb create mode 100644 test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_7_x_examples.rb diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index 7c0b2600652..e29acab01d7 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -69,6 +69,7 @@ def initialize(uuid=nil) "6.0" => Version_6_0, "6.1" => Version_6_1, "7.0" => Version_7_0, + "7.1" => Version_7_1, } if @@version.start_with?("4.2.14") diff --git a/plugins/providers/virtualbox/driver/version_7_1.rb b/plugins/providers/virtualbox/driver/version_7_1.rb new file mode 100644 index 00000000000..0f2e891b1fb --- /dev/null +++ b/plugins/providers/virtualbox/driver/version_7_1.rb @@ -0,0 +1,19 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +require File.expand_path("../version_7_0", __FILE__) + +module VagrantPlugins + module ProviderVirtualBox + module Driver + # Driver for VirtualBox 7.1.x + class Version_7_1 < Version_7_0 + def initialize(uuid) + super + + @logger = Log4r::Logger.new("vagrant::provider::virtualbox_7_1") + end + end + end + end +end diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 8c0ff08fb2c..25b49fd3160 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -104,6 +104,7 @@ module Driver autoload :Version_6_0, File.expand_path("../driver/version_6_0", __FILE__) autoload :Version_6_1, File.expand_path("../driver/version_6_1", __FILE__) autoload :Version_7_0, File.expand_path("../driver/version_7_0", __FILE__) + autoload :Version_7_1, File.expand_path("../driver/version_7_1", __FILE__) end module Model diff --git a/test/unit/plugins/providers/virtualbox/base.rb b/test/unit/plugins/providers/virtualbox/base.rb index bd53e6f5867..9a1ced190dd 100644 --- a/test/unit/plugins/providers/virtualbox/base.rb +++ b/test/unit/plugins/providers/virtualbox/base.rb @@ -7,3 +7,4 @@ require_relative "support/shared/virtualbox_driver_version_4_x_examples" require_relative "support/shared/virtualbox_driver_version_5_x_examples" require_relative "support/shared/virtualbox_driver_version_6_x_examples" +require_relative "support/shared/virtualbox_driver_version_7_x_examples" 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 5ecadbbe48d..f796c384cdf 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 @@ -13,6 +13,7 @@ it_behaves_like "a version 5.x virtualbox driver" it_behaves_like "a version 6.x virtualbox driver" + it_behaves_like "a version 7.x virtualbox driver" describe "#read_forwarded_ports" do let(:uuid) { "MACHINE-UUID" } @@ -83,774 +84,5 @@ subject.read_forwarded_ports end 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 - let(:bridgedifs) { VBOX_BRIDGEDIFS } - - before do - allow(subject).to receive(:execute).and_call_original - expect(subject). - to receive(:execute). - with("list", "bridgedifs"). - and_return(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 - - context "when address is empty" do - let(:bridgedifs) { VBOX_BRIDGEDIFS.sub("0.0.0.0", "") } - - it "should not raise an error" do - expect { subject.read_bridged_interfaces }.to_not raise_error - 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 - - it "should assign the address as the first in the subnet" do - result = subject.read_host_only_interfaces - expect(result.first[:ip]).to eq(IPAddr.new(result.first[:lowerip]).succ.to_s) - end - - context "when dhcp range is set" do - before do - allow(subject).to receive(:execute).with("list", "hostonlynets", any_args). - and_return(VBOX_RANGE_HOSTONLYNETS) - end - - it "should assign the address as the first in the dhcp range" do - result = subject.read_host_only_interfaces - expect(result.first[:ip]).to eq(result.first[:lowerip]) - end - 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 - - describe "#read_network_interfaces" do - before do - allow(subject) - .to receive(:execute). - with("showvminfo", any_args). - and_return(VBOX_GUEST_HOSTONLYVNETS_INFO) - end - - context "when hostonlynets is disabled" do - before do - allow(subject).to receive(:use_host_only_nets?).and_return(false) - end - - it "should return two interfaces" do - valid_interfaces = subject.read_network_interfaces.find_all { |k, v| - v[:type] != :none - } - expect(valid_interfaces.size).to eq(2) - end - - it "should include a nat type" do - expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :nat }).to be - end - - it "should include a hostonlynetwork type with no information" do - expect(subject.read_network_interfaces[2]).to eq({type: :hostonlynetwork}) - end - end - - context "when hostonlynets is enabled" do - before do - allow(subject).to receive(:use_host_only_nets?).and_return(true) - end - - it "should return two interfaces" do - valid_interfaces = subject.read_network_interfaces.find_all { |k, v| - v[:type] != :none - } - expect(valid_interfaces.size).to eq(2) - end - - it "should include a nat type" do - expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :nat }).to be - end - - it "should include a hostonly type" do - expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :hostonly }).to be - end - - it "should not include a hostonlynetwork type" do - expect(subject.read_network_interfaces.detect { |_, v| - v[:type] == :hostonlynetwork - }).to_not be - end - - it "should include the hostonly network name" do - hostonly = subject.read_network_interfaces.values.detect { |v| - v[:type] == :hostonly - } - expect(hostonly).to be - expect(hostonly[:hostonly]).to eq("vagrantnet-vbox1") - end - 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) - -VBOX_RANGE_HOSTONLYNETS=%(Name: vagrantnet-vbox1 -GUID: 10000000-0000-0000-0000-000000000000 - -State: Enabled -NetworkMask: 255.255.255.0 -LowerIP: 192.168.61.10 -UpperIP: 192.168.61.100 -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) - -VBOX_GUEST_HOSTONLYVNETS_INFO=%( -natnet1="nat" -macaddress1="080027BB1475" -cableconnected1="on" -nic1="nat" -nictype1="82540EM" -nicspeed1="0" -mtu="0" -sockSnd="64" -sockRcv="64" -tcpWndSnd="64" -tcpWndRcv="64" -Forwarding(0)="ssh,tcp,127.0.0.1,2222,,22" -hostonly-network2="vagrantnet-vbox1" -macaddress2="080027FBC15B" -cableconnected2="on" -nic2="hostonlynetwork" -nictype2="82540EM" -nicspeed2="0" -nic3="none" -nic4="none" -nic5="none" -nic6="none" -nic7="none" -nic8="none" -) diff --git a/test/unit/plugins/providers/virtualbox/driver/version_7_1_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_7_1_test.rb new file mode 100644 index 00000000000..075680e8c9f --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/driver/version_7_1_test.rb @@ -0,0 +1,15 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +require "stringio" +require_relative "../base" + +describe VagrantPlugins::ProviderVirtualBox::Driver::Version_7_1 do + include_context "virtualbox" + + subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_7_1.new(uuid) } + + it_behaves_like "a version 5.x virtualbox driver" + it_behaves_like "a version 6.x virtualbox driver" + it_behaves_like "a version 7.x virtualbox driver" +end diff --git a/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_7_x_examples.rb b/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_7_x_examples.rb new file mode 100644 index 00000000000..c885dee957c --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/support/shared/virtualbox_driver_version_7_x_examples.rb @@ -0,0 +1,777 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +shared_examples "a version 7.x virtualbox driver" do |opts| + before do + raise ArgumentError, "Need virtualbox context to use these shared examples." if !(defined? vbox_context) + 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 + let(:bridgedifs) { VBOX_BRIDGEDIFS } + + before do + allow(subject).to receive(:execute).and_call_original + expect(subject). + to receive(:execute). + with("list", "bridgedifs"). + and_return(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 + + context "when address is empty" do + let(:bridgedifs) { VBOX_BRIDGEDIFS.sub("0.0.0.0", "") } + + it "should not raise an error" do + expect { subject.read_bridged_interfaces }.to_not raise_error + 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 + + it "should assign the address as the first in the subnet" do + result = subject.read_host_only_interfaces + expect(result.first[:ip]).to eq(IPAddr.new(result.first[:lowerip]).succ.to_s) + end + + context "when dhcp range is set" do + before do + allow(subject).to receive(:execute).with("list", "hostonlynets", any_args). + and_return(VBOX_RANGE_HOSTONLYNETS) + end + + it "should assign the address as the first in the dhcp range" do + result = subject.read_host_only_interfaces + expect(result.first[:ip]).to eq(result.first[:lowerip]) + end + 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 + + describe "#read_network_interfaces" do + before do + allow(subject) + .to receive(:execute). + with("showvminfo", any_args). + and_return(VBOX_GUEST_HOSTONLYVNETS_INFO) + end + + context "when hostonlynets is disabled" do + before do + allow(subject).to receive(:use_host_only_nets?).and_return(false) + end + + it "should return two interfaces" do + valid_interfaces = subject.read_network_interfaces.find_all { |k, v| + v[:type] != :none + } + expect(valid_interfaces.size).to eq(2) + end + + it "should include a nat type" do + expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :nat }).to be + end + + it "should include a hostonlynetwork type with no information" do + expect(subject.read_network_interfaces[2]).to eq({type: :hostonlynetwork}) + end + end + + context "when hostonlynets is enabled" do + before do + allow(subject).to receive(:use_host_only_nets?).and_return(true) + end + + it "should return two interfaces" do + valid_interfaces = subject.read_network_interfaces.find_all { |k, v| + v[:type] != :none + } + expect(valid_interfaces.size).to eq(2) + end + + it "should include a nat type" do + expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :nat }).to be + end + + it "should include a hostonly type" do + expect(subject.read_network_interfaces.detect { |_, v| v[:type] == :hostonly }).to be + end + + it "should not include a hostonlynetwork type" do + expect(subject.read_network_interfaces.detect { |_, v| + v[:type] == :hostonlynetwork + }).to_not be + end + + it "should include the hostonly network name" do + hostonly = subject.read_network_interfaces.values.detect { |v| + v[:type] == :hostonly + } + expect(hostonly).to be + expect(hostonly[:hostonly]).to eq("vagrantnet-vbox1") + end + 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) + +VBOX_RANGE_HOSTONLYNETS=%(Name: vagrantnet-vbox1 +GUID: 10000000-0000-0000-0000-000000000000 + +State: Enabled +NetworkMask: 255.255.255.0 +LowerIP: 192.168.61.10 +UpperIP: 192.168.61.100 +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) + +VBOX_GUEST_HOSTONLYVNETS_INFO=%( +natnet1="nat" +macaddress1="080027BB1475" +cableconnected1="on" +nic1="nat" +nictype1="82540EM" +nicspeed1="0" +mtu="0" +sockSnd="64" +sockRcv="64" +tcpWndSnd="64" +tcpWndRcv="64" +Forwarding(0)="ssh,tcp,127.0.0.1,2222,,22" +hostonly-network2="vagrantnet-vbox1" +macaddress2="080027FBC15B" +cableconnected2="on" +nic2="hostonlynetwork" +nictype2="82540EM" +nicspeed2="0" +nic3="none" +nic4="none" +nic5="none" +nic6="none" +nic7="none" +nic8="none" +) diff --git a/website/content/docs/providers/virtualbox/index.mdx b/website/content/docs/providers/virtualbox/index.mdx index 96f47151c76..236127455e1 100644 --- a/website/content/docs/providers/virtualbox/index.mdx +++ b/website/content/docs/providers/virtualbox/index.mdx @@ -12,9 +12,9 @@ Vagrant comes with support out of the box for [VirtualBox](https://www.virtualbo a free, cross-platform consumer virtualization product. The VirtualBox provider is compatible with VirtualBox versions 4.0.x, 4.1.x, -4.2.x, 4.3.x, 5.0.x, 5.1.x, 5.2.x, 6.0.x, 6.1.x and 7.0.x. Other versions are unsupported and the provider -will display an error message. Please note that beta and pre-release versions -of VirtualBox are not supported and may not be well-behaved. +4.2.x, 4.3.x, 5.0.x, 5.1.x, 5.2.x, 6.0.x, 6.1.x, 7.0.x, and 7.1.x. Other versions +are unsupported and the provider will display an error message. Please note that +beta and pre-release versions VirtualBox are not supported and may not be well-behaved. VirtualBox must be installed on its own prior to using the provider, or the provider will display an error message asking you to install it.