Previously, we required a host-only interface with a static IP for NFS to work in VirtualBox, because we needed access to the guest's IP in order to properly configure mount commands. After boot, VirtualBox exposes the IP addresses of a guest's network adapters via the "guestproperty" interface. This adds support for reading VirtualBox guest properties to the VirtualBox driver and utilizes that support to prepare NFS settings, which removes the necessity for a static IP for NFS to work. In this commit we also start building out scaffolding for unit testing vbox actions and drivers. Test plan: - Prepare a Vagrantfile with the following: * private network with type: :dhcp * synced folder with nfs: true - Boot a VM from this Vagrantfile using the virtualbox provider - Machine should boot successfully with working synced folder
96 lines
3.5 KiB
Ruby
96 lines
3.5 KiB
Ruby
module VagrantPlugins
|
|
module ProviderVirtualBox
|
|
module Action
|
|
class PrepareNFSSettings
|
|
include Vagrant::Util::Retryable
|
|
|
|
def initialize(app,env)
|
|
@app = app
|
|
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
|
end
|
|
|
|
def call(env)
|
|
@machine = env[:machine]
|
|
@app.call(env)
|
|
|
|
if using_nfs?
|
|
@logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP")
|
|
add_nfs_settings_to_env!(env)
|
|
end
|
|
end
|
|
|
|
# We're using NFS if we have any synced folder with NFS configured. If
|
|
# we are not using NFS we don't need to do the extra work to
|
|
# populate these fields in the environment.
|
|
def using_nfs?
|
|
@machine.config.vm.synced_folders.any? { |_, opts| opts[:type] == :nfs }
|
|
end
|
|
|
|
# Extracts the proper host and guest IPs for NFS mounts and stores them
|
|
# in the environment for the SyncedFolder action to use them in
|
|
# mounting.
|
|
#
|
|
# The ! indicates that this method modifies its argument.
|
|
def add_nfs_settings_to_env!(env)
|
|
adapter, host_ip = find_host_only_adapter
|
|
machine_ip = nil
|
|
machine_ip = read_machine_ip(adapter) if adapter
|
|
|
|
raise Vagrant::Errors::NFSNoHostonlyNetwork if !host_ip || !machine_ip
|
|
|
|
env[:nfs_host_ip] = host_ip
|
|
env[:nfs_machine_ip] = machine_ip
|
|
end
|
|
|
|
# Finds first host only network adapter and returns its adapter number
|
|
# and IP address
|
|
#
|
|
# @return [Integer, String] adapter number, ip address of found host-only adapter
|
|
def find_host_only_adapter
|
|
@machine.provider.driver.read_network_interfaces.each do |adapter, opts|
|
|
if opts[:type] == :hostonly
|
|
@machine.provider.driver.read_host_only_interfaces.each do |interface|
|
|
if interface[:name] == opts[:hostonly]
|
|
return adapter, interface[:ip]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
# Returns the IP address of the guest by looking at vbox guest property
|
|
# for the appropriate guest adapter.
|
|
#
|
|
# For DHCP interfaces, the guest property will not be present until the
|
|
# guest completes
|
|
#
|
|
# @param [Integer] adapter number to read IP for
|
|
# @return [String] ip address of adapter
|
|
def read_machine_ip(adapter)
|
|
# vbox guest properties are 0-indexed, while showvminfo network
|
|
# interfaces are 1-indexed. go figure.
|
|
guestproperty_adapter = adapter - 1
|
|
|
|
# we need to wait for the guest's IP to show up as a guest property.
|
|
# retry thresholds are relatively high since we might need to wait
|
|
# for DHCP, but even static IPs can take a second or two to appear.
|
|
retryable(retry_options.merge(on: Vagrant::Errors::VirtualBoxGuestPropertyNotFound)) do
|
|
@machine.provider.driver.read_guest_ip(guestproperty_adapter)
|
|
end
|
|
rescue Vagrant::Errors::VirtualBoxGuestPropertyNotFound
|
|
# this error is more specific with a better error message directing
|
|
# the user towards the fact that it's probably a reportable bug
|
|
raise Vagrant::Errors::NFSNoGuestIP
|
|
end
|
|
|
|
# Separating these out so we can stub out the sleep in tests
|
|
def retry_options
|
|
{tries: 15, sleep: 1}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|