New Network VirtualBox middleware to handle the changes.
Note this is a WIP (hence committed on a branch)
This commit is contained in:
parent
8299ac38bd
commit
2d8f9baf7f
|
@ -23,7 +23,6 @@ module VagrantPlugins
|
|||
autoload :DestroyUnusedNetworkInterfaces, File.expand_path("../action/destroy_unused_network_interfaces", __FILE__)
|
||||
autoload :DiscardState, File.expand_path("../action/discard_state", __FILE__)
|
||||
autoload :Export, File.expand_path("../action/export", __FILE__)
|
||||
autoload :ForwardPorts, File.expand_path("../action/forward_ports", __FILE__)
|
||||
autoload :Halt, File.expand_path("../action/halt", __FILE__)
|
||||
autoload :HostName, File.expand_path("../action/host_name", __FILE__)
|
||||
autoload :Import, File.expand_path("../action/import", __FILE__)
|
||||
|
@ -59,14 +58,13 @@ module VagrantPlugins
|
|||
b.use ClearForwardedPorts
|
||||
b.use EnvSet, :port_collision_handler => :correct
|
||||
b.use CheckPortCollisions
|
||||
b.use ForwardPorts
|
||||
b.use Provision
|
||||
b.use PruneNFSExports
|
||||
b.use NFS
|
||||
b.use ClearSharedFolders
|
||||
b.use ShareFolders
|
||||
b.use ClearNetworkInterfaces
|
||||
#b.use Network
|
||||
b.use Network
|
||||
b.use HostName
|
||||
b.use SaneDefaults
|
||||
b.use Customize
|
||||
|
|
|
@ -7,397 +7,204 @@ require "vagrant/util/network_ip"
|
|||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
module Action
|
||||
# This middleware class sets up all networking for the VirtualBox
|
||||
# instance. This includes host only networks, bridged networking,
|
||||
# forwarded ports, etc.
|
||||
#
|
||||
# This handles all the `config.vm.network` configurations.
|
||||
class Network
|
||||
# Utilities to deal with network addresses
|
||||
include Vagrant::Util::NetworkIP
|
||||
|
||||
def initialize(app, env)
|
||||
@logger = Log4r::Logger.new("vagrant::action::vm::network")
|
||||
|
||||
@logger = Log4r::Logger.new("vagrant::plugins::virtualbox::network")
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# TODO: Validate network configuration prior to anything below
|
||||
@env = env
|
||||
|
||||
# First we have to get the array of adapters that we need
|
||||
# to create on the virtual machine itself, as well as the
|
||||
# driver-agnostic network configurations for each.
|
||||
@logger.debug("Determining adapters and networks...")
|
||||
adapters = []
|
||||
networks = []
|
||||
env[:machine].config.vm.networks.each do |type, args|
|
||||
# Get the normalized configuration we'll use around
|
||||
config = send("#{type}_config", args)
|
||||
# Get the list of network adapters from the configuration
|
||||
network_adapters_config = env[:machine].provider_config.network_adapters.dup
|
||||
|
||||
# Get the virtualbox adapter configuration
|
||||
# Assign the adapter slot for each high-level network
|
||||
available_slots = Set.new(1..8)
|
||||
network_adapters_config.each do |slot, _data|
|
||||
available_slots.delete(slot)
|
||||
end
|
||||
|
||||
@logger.debug("Available slots for high-level adapters: #{available_slots.inspect}")
|
||||
@logger.info("Determinging network adapters required for high-level configuration...")
|
||||
available_slots = available_slots.to_a.sort
|
||||
env[:machine].config.vm.networks.each do |type, args|
|
||||
# We only handle private and public networks
|
||||
next if type != :private_network && type != :public_network
|
||||
|
||||
options = nil
|
||||
options = args.last if args.last.is_a?(Hash)
|
||||
options ||= {}
|
||||
|
||||
# Figure out the slot that this adapter will go into
|
||||
slot = options[:virtualbox__adapter]
|
||||
if !slot
|
||||
if available_slots.empty?
|
||||
# TODO: Error that we have no room for this adapter
|
||||
end
|
||||
|
||||
slot = available_slots.shift
|
||||
end
|
||||
|
||||
# Configure it
|
||||
data = nil
|
||||
if type == :private_network
|
||||
# private_network = hostonly
|
||||
|
||||
config_args = [args[0]]
|
||||
data = [:hostonly, config_args]
|
||||
elsif type == :public_network
|
||||
# public_network = bridged
|
||||
|
||||
config_args = []
|
||||
data = [:bridged, config_args]
|
||||
end
|
||||
|
||||
# Store it!
|
||||
@logger.info(" -- Slot #{slot}: #{data[0]}")
|
||||
network_adapters_config[slot] = data
|
||||
end
|
||||
|
||||
@logger.info("Determining adapters and compiling network configuration...")
|
||||
adapters = []
|
||||
network_adapters_config.each do |slot, data|
|
||||
type = data[0]
|
||||
args = data[1]
|
||||
|
||||
@logger.info("Network slot #{slot}. Type: #{type}.")
|
||||
|
||||
# Get the normalized configuration for this type
|
||||
config = send("#{type}_config", args)
|
||||
config[:adapter] = slot
|
||||
|
||||
@logger.debug("Normalized configuration: #{config.inspect}")
|
||||
|
||||
# Get the VirtualBox adapter configuration
|
||||
adapter = send("#{type}_adapter", config)
|
||||
adapters << adapter
|
||||
|
||||
# Get the network configuration
|
||||
network = send("#{type}_network_config", config)
|
||||
network[:_auto_config] = true if config[:auto_config]
|
||||
networks << network
|
||||
@logger.debug("Adapter configuration: #{adapter.inspect}")
|
||||
end
|
||||
|
||||
if !adapters.empty?
|
||||
# Automatically assign an adapter number to any adapters
|
||||
# that aren't explicitly set.
|
||||
@logger.debug("Assigning adapter locations...")
|
||||
assign_adapter_locations(adapters)
|
||||
|
||||
# Verify that our adapters are good just prior to enabling them.
|
||||
verify_adapters(adapters)
|
||||
|
||||
# Create all the network interfaces
|
||||
# Enable the adapters
|
||||
@logger.info("Enabling adapters...")
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.network.preparing")
|
||||
env[:machine].provider.driver.enable_adapters(adapters)
|
||||
end
|
||||
|
||||
# Continue the middleware chain. We're done with our VM
|
||||
# setup until after it is booted.
|
||||
# Continue the middleware chain.
|
||||
@app.call(env)
|
||||
|
||||
if !adapters.empty? && !networks.empty?
|
||||
# Determine the interface numbers for the guest.
|
||||
assign_interface_numbers(networks, adapters)
|
||||
|
||||
# Configure all the network interfaces on the guest. We only
|
||||
# want to configure the networks that have `auto_config` setup.
|
||||
networks_to_configure = networks.select { |n| n[:_auto_config] }
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.network.configuring")
|
||||
env[:machine].guest.configure_networks(networks_to_configure)
|
||||
end
|
||||
end
|
||||
|
||||
# This method assigns the adapter to use for the adapter.
|
||||
# e.g. it says that the first adapter is actually on the
|
||||
# virtual machine's 2nd adapter location.
|
||||
#
|
||||
# It determines the adapter numbers by simply finding the
|
||||
# "next available" in each case.
|
||||
#
|
||||
# The adapters are modified in place by adding an ":adapter"
|
||||
# field to each.
|
||||
def assign_adapter_locations(adapters)
|
||||
available = Set.new(1..8)
|
||||
|
||||
# Determine which NICs are actually available.
|
||||
interfaces = @env[:machine].provider.driver.read_network_interfaces
|
||||
interfaces.each do |number, nic|
|
||||
# Remove the number from the available NICs if the
|
||||
# NIC is in use.
|
||||
available.delete(number) if nic[:type] != :none
|
||||
end
|
||||
|
||||
# Based on the available set, assign in order to
|
||||
# the adapters.
|
||||
available = available.to_a.sort
|
||||
@logger.debug("Available NICs: #{available.inspect}")
|
||||
adapters.each do |adapter|
|
||||
# Ignore the adapters that already have been assigned
|
||||
if !adapter[:adapter]
|
||||
# If we have no available adapters, then that is an exceptional
|
||||
# event.
|
||||
raise Vagrant::Errors::NetworkNoAdapters if available.empty?
|
||||
|
||||
# Otherwise, assign as the adapter the next available item
|
||||
adapter[:adapter] = available.shift
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Verifies that the adapter configurations look good. This will
|
||||
# raise an exception in the case that any errors occur.
|
||||
def verify_adapters(adapters)
|
||||
# Verify that there are no collisions in the adapters being used.
|
||||
used = Set.new
|
||||
adapters.each do |adapter|
|
||||
raise Vagrant::Errors::NetworkAdapterCollision if used.include?(adapter[:adapter])
|
||||
used.add(adapter[:adapter])
|
||||
end
|
||||
end
|
||||
|
||||
# Assigns the actual interface number of a network based on the
|
||||
# enabled NICs on the virtual machine.
|
||||
#
|
||||
# This interface number is used by the guest to configure the
|
||||
# NIC on the guest VM.
|
||||
#
|
||||
# The networks are modified in place by adding an ":interface"
|
||||
# field to each.
|
||||
def assign_interface_numbers(networks, adapters)
|
||||
current = 0
|
||||
adapter_to_interface = {}
|
||||
|
||||
# Make a first pass to assign interface numbers by adapter location
|
||||
vm_adapters = @env[:machine].provider.driver.read_network_interfaces
|
||||
vm_adapters.sort.each do |number, adapter|
|
||||
if adapter[:type] != :none
|
||||
# Not used, so assign the interface number and increment
|
||||
adapter_to_interface[number] = current
|
||||
current += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Make a pass through the adapters to assign the :interface
|
||||
# key to each network configuration.
|
||||
adapters.each_index do |i|
|
||||
adapter = adapters[i]
|
||||
network = networks[i]
|
||||
|
||||
# Figure out the interface number by simple lookup
|
||||
network[:interface] = adapter_to_interface[adapter[:adapter]]
|
||||
end
|
||||
# TODO: Configure running-VM networks
|
||||
end
|
||||
|
||||
def hostonly_config(args)
|
||||
ip = args[0]
|
||||
options = args[1] || {}
|
||||
|
||||
# Determine if we're dealing with a static IP or a DHCP-served IP.
|
||||
type = ip == :dhcp ? :dhcp : :static
|
||||
|
||||
# Default IP is in the 20-bit private network block for DHCP based networks
|
||||
ip = "172.28.128.1" if type == :dhcp
|
||||
|
||||
options = {
|
||||
:type => type,
|
||||
:ip => ip,
|
||||
:netmask => "255.255.255.0",
|
||||
:adapter => nil,
|
||||
:mac => nil,
|
||||
:name => nil,
|
||||
:auto_config => true
|
||||
}.merge(options)
|
||||
:netmask => "255.255.255.0"
|
||||
}.merge(args[1] || {})
|
||||
|
||||
# Verify that this hostonly network wouldn't conflict with any
|
||||
# bridged interfaces
|
||||
verify_no_bridge_collision(options)
|
||||
# Calculate our network address for the given IP/netmask
|
||||
netaddr = network_address(ip, options[:netmask])
|
||||
|
||||
# Get the network address and IP parts which are used for many
|
||||
# default calculations
|
||||
netaddr = network_address(options[:ip], options[:netmask])
|
||||
# Verify that a host-only network subnet would not collide
|
||||
# with a bridged networking interface.
|
||||
#
|
||||
# If the subnets overlap in any way then the host only network
|
||||
# will not work because the routing tables will force the
|
||||
# traffic onto the real interface rather than the VirtualBox
|
||||
# interface.
|
||||
@env[:machine].provider.driver.read_bridged_interfaces.each do |interface|
|
||||
that_netaddr = network_address(interface[:ip], interface[:netmask])
|
||||
raise Vagrant::Errors::NetworkCollision if \
|
||||
netaddr == that_netaddr && interface[:status] != "Down"
|
||||
end
|
||||
|
||||
# Split the IP address into its components
|
||||
ip_parts = netaddr.split(".").map { |i| i.to_i }
|
||||
|
||||
# Calculate the adapter IP, which we assume is the IP ".1" at the
|
||||
# end usually.
|
||||
# Calculate the adapter IP, which we assume is the IP ".1" at
|
||||
# the end usually.
|
||||
adapter_ip = ip_parts.dup
|
||||
adapter_ip[3] += 1
|
||||
options[:adapter_ip] ||= adapter_ip.join(".")
|
||||
|
||||
if type == :dhcp
|
||||
# Calculate the DHCP server IP, which is the network address
|
||||
# with the final octet + 2. So "172.28.0.0" turns into "172.28.0.2"
|
||||
dhcp_ip = ip_parts.dup
|
||||
dhcp_ip[3] += 2
|
||||
options[:dhcp_ip] ||= dhcp_ip.join(".")
|
||||
|
||||
# Calculate the lower and upper bound for the DHCP server
|
||||
dhcp_lower = ip_parts.dup
|
||||
dhcp_lower[3] += 3
|
||||
options[:dhcp_lower] ||= dhcp_lower.join(".")
|
||||
|
||||
dhcp_upper = ip_parts.dup
|
||||
dhcp_upper[3] = 254
|
||||
options[:dhcp_upper] ||= dhcp_upper.join(".")
|
||||
end
|
||||
|
||||
# Return the hostonly network configuration
|
||||
return options
|
||||
return {
|
||||
:adapter_ip => adapter_ip,
|
||||
:ip => ip,
|
||||
:netmask => options[:netmask],
|
||||
:type => :static
|
||||
}
|
||||
end
|
||||
|
||||
def hostonly_adapter(config)
|
||||
@logger.debug("Searching for matching network: #{config[:ip]}")
|
||||
interface = find_matching_hostonly_network(config)
|
||||
@logger.info("Searching for matching hostonly network: #{config[:ip]}")
|
||||
interface = hostonly_find_matching_network(config)
|
||||
|
||||
if !interface
|
||||
@logger.debug("Network not found. Creating if we can.")
|
||||
@logger.info("Network not found. Creating if we can.")
|
||||
|
||||
# It is an error case if a specific name was given but the network
|
||||
# doesn't exist.
|
||||
# It is an error if a specific host only network name was specified
|
||||
# but the network wasn't found.
|
||||
if config[:name]
|
||||
raise Vagrant::Errors::NetworkNotFound, :name => config[:name]
|
||||
end
|
||||
|
||||
# Otherwise, we create a new network and put the net network
|
||||
# in the list of available networks so other network definitions
|
||||
# can use it!
|
||||
interface = create_hostonly_network(config)
|
||||
@logger.debug("Created network: #{interface[:name]}")
|
||||
end
|
||||
|
||||
if config[:type] == :dhcp
|
||||
# Check that if there is a DHCP server attached on our interface,
|
||||
# then it is identical. Otherwise, we can't set it.
|
||||
if interface[:dhcp]
|
||||
valid = interface[:dhcp][:ip] == config[:dhcp_ip] &&
|
||||
interface[:dhcp][:lower] == config[:dhcp_lower] &&
|
||||
interface[:dhcp][:upper] == config[:dhcp_upper]
|
||||
|
||||
raise Vagrant::Errors::NetworkDHCPAlreadyAttached if !valid
|
||||
|
||||
@logger.debug("DHCP server already properly configured")
|
||||
else
|
||||
# Configure the DHCP server for the network.
|
||||
@logger.debug("Creating a DHCP server...")
|
||||
@env[:machine].provider.driver.create_dhcp_server(interface[:name], config)
|
||||
end
|
||||
# Create a new network
|
||||
interface = hostonly_create_network(config)
|
||||
@logger.info("Created network: #{interface[:name]}")
|
||||
end
|
||||
|
||||
return {
|
||||
:adapter => config[:adapter],
|
||||
:type => :hostonly,
|
||||
:hostonly => interface[:name],
|
||||
:mac_address => config[:mac],
|
||||
:nic_type => config[:nic_type]
|
||||
:hostonly => interface[:name]
|
||||
}
|
||||
end
|
||||
|
||||
def hostonly_network_config(config)
|
||||
def nat_config(options)
|
||||
return {}
|
||||
end
|
||||
|
||||
def nat_adapter(config)
|
||||
return {
|
||||
:type => config[:type],
|
||||
:adapter_ip => config[:adapter_ip],
|
||||
:ip => config[:ip],
|
||||
:netmask => config[:netmask]
|
||||
:adapter => config[:adapter],
|
||||
:type => :nat,
|
||||
}
|
||||
end
|
||||
|
||||
# Creates a new hostonly network that matches the network requested
|
||||
# by the given host-only network configuration.
|
||||
def create_hostonly_network(config)
|
||||
# Create the options that are going to be used to create our
|
||||
# new network.
|
||||
options = config.dup
|
||||
options[:ip] = options[:adapter_ip]
|
||||
|
||||
@env[:machine].provider.driver.create_host_only_network(options)
|
||||
#-----------------------------------------------------------------
|
||||
# Hostonly Helper Functions
|
||||
#-----------------------------------------------------------------
|
||||
# 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]
|
||||
)
|
||||
end
|
||||
|
||||
# Finds a host only network that matches our configuration on VirtualBox.
|
||||
# This will return nil if a matching network does not exist.
|
||||
def find_matching_hostonly_network(config)
|
||||
# This finds a matching host only network for the given configuration.
|
||||
def hostonly_find_matching_network(config)
|
||||
this_netaddr = network_address(config[:ip], config[:netmask])
|
||||
|
||||
@env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
|
||||
if config[:name] && config[:name] == interface[:name]
|
||||
return interface
|
||||
elsif this_netaddr == network_address(interface[:ip], interface[:netmask])
|
||||
return interface
|
||||
end
|
||||
return interface if config[:name] && config[:name] == interface[:name]
|
||||
return interface if this_netaddr == \
|
||||
network_address(interface[:ip], interface[:netmask])
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Verifies that a host-only network subnet would not collide with
|
||||
# a bridged networking interface.
|
||||
#
|
||||
# If the subnets overlap in any way then the host only network
|
||||
# will not work because the routing tables will force the traffic
|
||||
# onto the real interface rather than the virtualbox interface.
|
||||
def verify_no_bridge_collision(options)
|
||||
this_netaddr = network_address(options[:ip], options[:netmask])
|
||||
|
||||
@env[:machine].provider.driver.read_bridged_interfaces.each do |interface|
|
||||
that_netaddr = network_address(interface[:ip], interface[:netmask])
|
||||
raise Vagrant::Errors::NetworkCollision if this_netaddr == that_netaddr && interface[:status] != "Down"
|
||||
end
|
||||
end
|
||||
|
||||
def bridged_config(args)
|
||||
options = args[0] || {}
|
||||
options = {} if !options.is_a?(Hash)
|
||||
|
||||
return {
|
||||
:adapter => nil,
|
||||
:mac => nil,
|
||||
:bridge => nil,
|
||||
:auto_config => true,
|
||||
:use_dhcp_assigned_default_route => false
|
||||
}.merge(options)
|
||||
end
|
||||
|
||||
def bridged_adapter(config)
|
||||
# Find the bridged interfaces that are available
|
||||
bridgedifs = @env[:machine].provider.driver.read_bridged_interfaces
|
||||
bridgedifs.delete_if { |interface| interface[:status] == "Down" }
|
||||
|
||||
# The name of the chosen bridge interface will be assigned to this
|
||||
# variable.
|
||||
chosen_bridge = nil
|
||||
|
||||
if config[:bridge]
|
||||
@logger.debug("Bridge was directly specified in config, searching for: #{config[:bridge]}")
|
||||
|
||||
# Search for a matching bridged interface
|
||||
bridgedifs.each do |interface|
|
||||
if interface[:name].downcase == config[:bridge].downcase
|
||||
@logger.debug("Specific bridge found as configured in the Vagrantfile. Using it.")
|
||||
chosen_bridge = interface[:name]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# If one wasn't found, then we notify the user here.
|
||||
if !chosen_bridge
|
||||
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.specific_not_found",
|
||||
:bridge => config[:bridge])
|
||||
end
|
||||
end
|
||||
|
||||
# If we still don't have a bridge chosen (this means that one wasn't
|
||||
# specified in the Vagrantfile, or the bridge specified in the Vagrantfile
|
||||
# wasn't found), then we fall back to the normal means of searchign for a
|
||||
# bridged network.
|
||||
if !chosen_bridge
|
||||
if bridgedifs.length == 1
|
||||
# One bridgable interface? Just use it.
|
||||
chosen_bridge = bridgedifs[0][:name]
|
||||
@logger.debug("Only one bridged interface available. Using it by default.")
|
||||
else
|
||||
# More than one bridgable interface requires a user decision, so
|
||||
# show options to choose from.
|
||||
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.available",
|
||||
:prefix => false)
|
||||
bridgedifs.each_index do |index|
|
||||
interface = bridgedifs[index]
|
||||
@env[:ui].info("#{index + 1}) #{interface[:name]}", :prefix => false)
|
||||
end
|
||||
|
||||
# The range of valid choices
|
||||
valid = Range.new(1, bridgedifs.length)
|
||||
|
||||
# The choice that the user has chosen as the bridging interface
|
||||
choice = nil
|
||||
while !valid.include?(choice)
|
||||
choice = @env[:ui].ask("What interface should the network bridge to? ")
|
||||
choice = choice.to_i
|
||||
end
|
||||
|
||||
chosen_bridge = bridgedifs[choice - 1][:name]
|
||||
end
|
||||
end
|
||||
|
||||
@logger.info("Bridging adapter #{config[:adapter]} to #{chosen_bridge}")
|
||||
|
||||
# Given the choice we can now define the adapter we're using
|
||||
return {
|
||||
:adapter => config[:adapter],
|
||||
:type => :bridged,
|
||||
:bridge => chosen_bridge,
|
||||
:mac_address => config[:mac],
|
||||
:nic_type => config[:nic_type]
|
||||
}
|
||||
end
|
||||
|
||||
def bridged_network_config(config)
|
||||
return {
|
||||
:type => :dhcp,
|
||||
:use_dhcp_assigned_default_route => config[:use_dhcp_assigned_default_route]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,404 @@
|
|||
require "set"
|
||||
|
||||
require "log4r"
|
||||
|
||||
require "vagrant/util/network_ip"
|
||||
|
||||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
module Action
|
||||
class Network
|
||||
# Utilities to deal with network addresses
|
||||
include Vagrant::Util::NetworkIP
|
||||
|
||||
def initialize(app, env)
|
||||
@logger = Log4r::Logger.new("vagrant::action::vm::network")
|
||||
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@env = env
|
||||
|
||||
# First we have to get the array of adapters that we need
|
||||
# to create on the virtual machine itself, as well as the
|
||||
# driver-agnostic network configurations for each.
|
||||
@logger.debug("Determining adapters and networks...")
|
||||
adapters = []
|
||||
networks = []
|
||||
env[:machine].config.vm.networks.each do |type, args|
|
||||
# Get the normalized configuration we'll use around
|
||||
config = send("#{type}_config", args)
|
||||
|
||||
# Get the virtualbox adapter configuration
|
||||
adapter = send("#{type}_adapter", config)
|
||||
adapters << adapter
|
||||
|
||||
# Get the network configuration
|
||||
network = send("#{type}_network_config", config)
|
||||
network[:_auto_config] = true if config[:auto_config]
|
||||
networks << network
|
||||
end
|
||||
|
||||
if !adapters.empty?
|
||||
# Automatically assign an adapter number to any adapters
|
||||
# that aren't explicitly set.
|
||||
@logger.debug("Assigning adapter locations...")
|
||||
assign_adapter_locations(adapters)
|
||||
|
||||
# Verify that our adapters are good just prior to enabling them.
|
||||
verify_adapters(adapters)
|
||||
|
||||
# Create all the network interfaces
|
||||
@logger.info("Enabling adapters...")
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.network.preparing")
|
||||
env[:machine].provider.driver.enable_adapters(adapters)
|
||||
end
|
||||
|
||||
# Continue the middleware chain. We're done with our VM
|
||||
# setup until after it is booted.
|
||||
@app.call(env)
|
||||
|
||||
if !adapters.empty? && !networks.empty?
|
||||
# Determine the interface numbers for the guest.
|
||||
assign_interface_numbers(networks, adapters)
|
||||
|
||||
# Configure all the network interfaces on the guest. We only
|
||||
# want to configure the networks that have `auto_config` setup.
|
||||
networks_to_configure = networks.select { |n| n[:_auto_config] }
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.network.configuring")
|
||||
env[:machine].guest.configure_networks(networks_to_configure)
|
||||
end
|
||||
end
|
||||
|
||||
# This method assigns the adapter to use for the adapter.
|
||||
# e.g. it says that the first adapter is actually on the
|
||||
# virtual machine's 2nd adapter location.
|
||||
#
|
||||
# It determines the adapter numbers by simply finding the
|
||||
# "next available" in each case.
|
||||
#
|
||||
# The adapters are modified in place by adding an ":adapter"
|
||||
# field to each.
|
||||
def assign_adapter_locations(adapters)
|
||||
available = Set.new(1..8)
|
||||
|
||||
# Determine which NICs are actually available.
|
||||
interfaces = @env[:machine].provider.driver.read_network_interfaces
|
||||
interfaces.each do |number, nic|
|
||||
# Remove the number from the available NICs if the
|
||||
# NIC is in use.
|
||||
available.delete(number) if nic[:type] != :none
|
||||
end
|
||||
|
||||
# Based on the available set, assign in order to
|
||||
# the adapters.
|
||||
available = available.to_a.sort
|
||||
@logger.debug("Available NICs: #{available.inspect}")
|
||||
adapters.each do |adapter|
|
||||
# Ignore the adapters that already have been assigned
|
||||
if !adapter[:adapter]
|
||||
# If we have no available adapters, then that is an exceptional
|
||||
# event.
|
||||
raise Vagrant::Errors::NetworkNoAdapters if available.empty?
|
||||
|
||||
# Otherwise, assign as the adapter the next available item
|
||||
adapter[:adapter] = available.shift
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Verifies that the adapter configurations look good. This will
|
||||
# raise an exception in the case that any errors occur.
|
||||
def verify_adapters(adapters)
|
||||
# Verify that there are no collisions in the adapters being used.
|
||||
used = Set.new
|
||||
adapters.each do |adapter|
|
||||
raise Vagrant::Errors::NetworkAdapterCollision if used.include?(adapter[:adapter])
|
||||
used.add(adapter[:adapter])
|
||||
end
|
||||
end
|
||||
|
||||
# Assigns the actual interface number of a network based on the
|
||||
# enabled NICs on the virtual machine.
|
||||
#
|
||||
# This interface number is used by the guest to configure the
|
||||
# NIC on the guest VM.
|
||||
#
|
||||
# The networks are modified in place by adding an ":interface"
|
||||
# field to each.
|
||||
def assign_interface_numbers(networks, adapters)
|
||||
current = 0
|
||||
adapter_to_interface = {}
|
||||
|
||||
# Make a first pass to assign interface numbers by adapter location
|
||||
vm_adapters = @env[:machine].provider.driver.read_network_interfaces
|
||||
vm_adapters.sort.each do |number, adapter|
|
||||
if adapter[:type] != :none
|
||||
# Not used, so assign the interface number and increment
|
||||
adapter_to_interface[number] = current
|
||||
current += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Make a pass through the adapters to assign the :interface
|
||||
# key to each network configuration.
|
||||
adapters.each_index do |i|
|
||||
adapter = adapters[i]
|
||||
network = networks[i]
|
||||
|
||||
# Figure out the interface number by simple lookup
|
||||
network[:interface] = adapter_to_interface[adapter[:adapter]]
|
||||
end
|
||||
end
|
||||
|
||||
def hostonly_config(args)
|
||||
ip = args[0]
|
||||
options = args[1] || {}
|
||||
|
||||
# Determine if we're dealing with a static IP or a DHCP-served IP.
|
||||
type = ip == :dhcp ? :dhcp : :static
|
||||
|
||||
# Default IP is in the 20-bit private network block for DHCP based networks
|
||||
ip = "172.28.128.1" if type == :dhcp
|
||||
|
||||
options = {
|
||||
:type => type,
|
||||
:ip => ip,
|
||||
:netmask => "255.255.255.0",
|
||||
:adapter => nil,
|
||||
:mac => nil,
|
||||
:name => nil,
|
||||
:auto_config => true
|
||||
}.merge(options)
|
||||
|
||||
# Verify that this hostonly network wouldn't conflict with any
|
||||
# bridged interfaces
|
||||
verify_no_bridge_collision(options)
|
||||
|
||||
# Get the network address and IP parts which are used for many
|
||||
# default calculations
|
||||
netaddr = network_address(options[:ip], options[:netmask])
|
||||
ip_parts = netaddr.split(".").map { |i| i.to_i }
|
||||
|
||||
# Calculate the adapter IP, which we assume is the IP ".1" at the
|
||||
# end usually.
|
||||
adapter_ip = ip_parts.dup
|
||||
adapter_ip[3] += 1
|
||||
options[:adapter_ip] ||= adapter_ip.join(".")
|
||||
|
||||
if type == :dhcp
|
||||
# Calculate the DHCP server IP, which is the network address
|
||||
# with the final octet + 2. So "172.28.0.0" turns into "172.28.0.2"
|
||||
dhcp_ip = ip_parts.dup
|
||||
dhcp_ip[3] += 2
|
||||
options[:dhcp_ip] ||= dhcp_ip.join(".")
|
||||
|
||||
# Calculate the lower and upper bound for the DHCP server
|
||||
dhcp_lower = ip_parts.dup
|
||||
dhcp_lower[3] += 3
|
||||
options[:dhcp_lower] ||= dhcp_lower.join(".")
|
||||
|
||||
dhcp_upper = ip_parts.dup
|
||||
dhcp_upper[3] = 254
|
||||
options[:dhcp_upper] ||= dhcp_upper.join(".")
|
||||
end
|
||||
|
||||
# Return the hostonly network configuration
|
||||
return options
|
||||
end
|
||||
|
||||
def hostonly_adapter(config)
|
||||
@logger.debug("Searching for matching network: #{config[:ip]}")
|
||||
interface = find_matching_hostonly_network(config)
|
||||
|
||||
if !interface
|
||||
@logger.debug("Network not found. Creating if we can.")
|
||||
|
||||
# It is an error case if a specific name was given but the network
|
||||
# doesn't exist.
|
||||
if config[:name]
|
||||
raise Vagrant::Errors::NetworkNotFound, :name => config[:name]
|
||||
end
|
||||
|
||||
# Otherwise, we create a new network and put the net network
|
||||
# in the list of available networks so other network definitions
|
||||
# can use it!
|
||||
interface = create_hostonly_network(config)
|
||||
@logger.debug("Created network: #{interface[:name]}")
|
||||
end
|
||||
|
||||
if config[:type] == :dhcp
|
||||
# Check that if there is a DHCP server attached on our interface,
|
||||
# then it is identical. Otherwise, we can't set it.
|
||||
if interface[:dhcp]
|
||||
valid = interface[:dhcp][:ip] == config[:dhcp_ip] &&
|
||||
interface[:dhcp][:lower] == config[:dhcp_lower] &&
|
||||
interface[:dhcp][:upper] == config[:dhcp_upper]
|
||||
|
||||
raise Vagrant::Errors::NetworkDHCPAlreadyAttached if !valid
|
||||
|
||||
@logger.debug("DHCP server already properly configured")
|
||||
else
|
||||
# Configure the DHCP server for the network.
|
||||
@logger.debug("Creating a DHCP server...")
|
||||
@env[:machine].provider.driver.create_dhcp_server(interface[:name], config)
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
:adapter => config[:adapter],
|
||||
:type => :hostonly,
|
||||
:hostonly => interface[:name],
|
||||
:mac_address => config[:mac],
|
||||
:nic_type => config[:nic_type]
|
||||
}
|
||||
end
|
||||
|
||||
def hostonly_network_config(config)
|
||||
return {
|
||||
:type => config[:type],
|
||||
:adapter_ip => config[:adapter_ip],
|
||||
:ip => config[:ip],
|
||||
:netmask => config[:netmask]
|
||||
}
|
||||
end
|
||||
|
||||
# Creates a new hostonly network that matches the network requested
|
||||
# by the given host-only network configuration.
|
||||
def create_hostonly_network(config)
|
||||
# Create the options that are going to be used to create our
|
||||
# new network.
|
||||
options = config.dup
|
||||
options[:ip] = options[:adapter_ip]
|
||||
|
||||
@env[:machine].provider.driver.create_host_only_network(options)
|
||||
end
|
||||
|
||||
# Finds a host only network that matches our configuration on VirtualBox.
|
||||
# This will return nil if a matching network does not exist.
|
||||
def find_matching_hostonly_network(config)
|
||||
this_netaddr = network_address(config[:ip], config[:netmask])
|
||||
|
||||
@env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
|
||||
if config[:name] && config[:name] == interface[:name]
|
||||
return interface
|
||||
elsif this_netaddr == network_address(interface[:ip], interface[:netmask])
|
||||
return interface
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Verifies that a host-only network subnet would not collide with
|
||||
# a bridged networking interface.
|
||||
#
|
||||
# If the subnets overlap in any way then the host only network
|
||||
# will not work because the routing tables will force the traffic
|
||||
# onto the real interface rather than the virtualbox interface.
|
||||
def verify_no_bridge_collision(options)
|
||||
this_netaddr = network_address(options[:ip], options[:netmask])
|
||||
|
||||
@env[:machine].provider.driver.read_bridged_interfaces.each do |interface|
|
||||
that_netaddr = network_address(interface[:ip], interface[:netmask])
|
||||
raise Vagrant::Errors::NetworkCollision if this_netaddr == that_netaddr && interface[:status] != "Down"
|
||||
end
|
||||
end
|
||||
|
||||
def bridged_config(args)
|
||||
options = args[0] || {}
|
||||
options = {} if !options.is_a?(Hash)
|
||||
|
||||
return {
|
||||
:adapter => nil,
|
||||
:mac => nil,
|
||||
:bridge => nil,
|
||||
:auto_config => true,
|
||||
:use_dhcp_assigned_default_route => false
|
||||
}.merge(options)
|
||||
end
|
||||
|
||||
def bridged_adapter(config)
|
||||
# Find the bridged interfaces that are available
|
||||
bridgedifs = @env[:machine].provider.driver.read_bridged_interfaces
|
||||
bridgedifs.delete_if { |interface| interface[:status] == "Down" }
|
||||
|
||||
# The name of the chosen bridge interface will be assigned to this
|
||||
# variable.
|
||||
chosen_bridge = nil
|
||||
|
||||
if config[:bridge]
|
||||
@logger.debug("Bridge was directly specified in config, searching for: #{config[:bridge]}")
|
||||
|
||||
# Search for a matching bridged interface
|
||||
bridgedifs.each do |interface|
|
||||
if interface[:name].downcase == config[:bridge].downcase
|
||||
@logger.debug("Specific bridge found as configured in the Vagrantfile. Using it.")
|
||||
chosen_bridge = interface[:name]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# If one wasn't found, then we notify the user here.
|
||||
if !chosen_bridge
|
||||
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.specific_not_found",
|
||||
:bridge => config[:bridge])
|
||||
end
|
||||
end
|
||||
|
||||
# If we still don't have a bridge chosen (this means that one wasn't
|
||||
# specified in the Vagrantfile, or the bridge specified in the Vagrantfile
|
||||
# wasn't found), then we fall back to the normal means of searchign for a
|
||||
# bridged network.
|
||||
if !chosen_bridge
|
||||
if bridgedifs.length == 1
|
||||
# One bridgable interface? Just use it.
|
||||
chosen_bridge = bridgedifs[0][:name]
|
||||
@logger.debug("Only one bridged interface available. Using it by default.")
|
||||
else
|
||||
# More than one bridgable interface requires a user decision, so
|
||||
# show options to choose from.
|
||||
@env[:ui].info I18n.t("vagrant.actions.vm.bridged_networking.available",
|
||||
:prefix => false)
|
||||
bridgedifs.each_index do |index|
|
||||
interface = bridgedifs[index]
|
||||
@env[:ui].info("#{index + 1}) #{interface[:name]}", :prefix => false)
|
||||
end
|
||||
|
||||
# The range of valid choices
|
||||
valid = Range.new(1, bridgedifs.length)
|
||||
|
||||
# The choice that the user has chosen as the bridging interface
|
||||
choice = nil
|
||||
while !valid.include?(choice)
|
||||
choice = @env[:ui].ask("What interface should the network bridge to? ")
|
||||
choice = choice.to_i
|
||||
end
|
||||
|
||||
chosen_bridge = bridgedifs[choice - 1][:name]
|
||||
end
|
||||
end
|
||||
|
||||
@logger.info("Bridging adapter #{config[:adapter]} to #{chosen_bridge}")
|
||||
|
||||
# Given the choice we can now define the adapter we're using
|
||||
return {
|
||||
:adapter => config[:adapter],
|
||||
:type => :bridged,
|
||||
:bridge => chosen_bridge,
|
||||
:mac_address => config[:mac],
|
||||
:nic_type => config[:nic_type]
|
||||
}
|
||||
end
|
||||
|
||||
def bridged_network_config(config)
|
||||
return {
|
||||
:type => :dhcp,
|
||||
:use_dhcp_assigned_default_route => config[:use_dhcp_assigned_default_route]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +1,28 @@
|
|||
module VagrantPlugins
|
||||
module ProviderVirtualBox
|
||||
class Config < Vagrant.plugin("2", :config)
|
||||
# An array of customizations to make on the VM prior to booting it.
|
||||
#
|
||||
# @return [Array]
|
||||
attr_reader :customizations
|
||||
|
||||
# If set to `true`, then VirtualBox will be launched with a GUI.
|
||||
#
|
||||
# @return [Boolean]
|
||||
attr_accessor :gui
|
||||
|
||||
# The defined network adapters.
|
||||
#
|
||||
# @return [Hash]
|
||||
attr_reader :network_adapters
|
||||
|
||||
def initialize
|
||||
@customizations = []
|
||||
@network_adapters = {}
|
||||
@gui = UNSET_VALUE
|
||||
|
||||
# We require that network adapter 1 is a NAT device.
|
||||
network_adapter(1, :nat)
|
||||
end
|
||||
|
||||
# Customize the VM by calling `VBoxManage` with the given
|
||||
|
@ -27,6 +41,15 @@ module VagrantPlugins
|
|||
@customizations << command
|
||||
end
|
||||
|
||||
# This defines a network adapter that will be added to the VirtualBox
|
||||
# virtual machine in the given slot.
|
||||
#
|
||||
# @param [Integer] slot The slot for this network adapter.
|
||||
# @param [Symbol] type The type of adapter.
|
||||
def network_adapter(slot, type, *args)
|
||||
@network_adapters[slot] = [type, args]
|
||||
end
|
||||
|
||||
# This is the hook that is called to finalize the object before it
|
||||
# is put into use.
|
||||
def finalize!
|
||||
|
|
Loading…
Reference in New Issue