Networking middleware
This commit is contained in:
parent
f205a747c9
commit
7d6c0db4ae
|
@ -13,6 +13,7 @@ module Vagrant
|
||||||
use VM::Customize
|
use VM::Customize
|
||||||
use VM::ForwardPorts
|
use VM::ForwardPorts
|
||||||
use VM::ShareFolders
|
use VM::ShareFolders
|
||||||
|
use VM::Network
|
||||||
use VM::Boot
|
use VM::Boot
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
module Vagrant
|
||||||
|
class Action
|
||||||
|
module VM
|
||||||
|
# Networking middleware for Vagrant. This enables host only
|
||||||
|
# networking on VMs if configured as such.
|
||||||
|
class Network
|
||||||
|
def initialize(app, env)
|
||||||
|
@app = app
|
||||||
|
@env = env
|
||||||
|
|
||||||
|
env["config"].vm.network_options.compact.each do |network_options|
|
||||||
|
if !verify_no_bridge_collision(network_options)
|
||||||
|
env.error!(:network_collides)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
@env = env
|
||||||
|
assign_network if enable_network?
|
||||||
|
|
||||||
|
@app.call(env)
|
||||||
|
|
||||||
|
if !env.error? && enable_network?
|
||||||
|
@env.logger.info "Enabling host only network..."
|
||||||
|
@env["vm"].system.prepare_host_only_network
|
||||||
|
@env.env.config.vm.network_options.compact.each do |network_options|
|
||||||
|
@env["vm"].system.enable_host_only_network(network_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verifies that there is no collision with a bridged network interface
|
||||||
|
# for the given network options.
|
||||||
|
def verify_no_bridge_collision(net_options)
|
||||||
|
interfaces = VirtualBox::Global.global.host.network_interfaces
|
||||||
|
interfaces.each do |ni|
|
||||||
|
next if ni.interface_type == :host_only
|
||||||
|
|
||||||
|
result = if net_options[:name]
|
||||||
|
true if net_options[:name] == ni.name
|
||||||
|
else
|
||||||
|
true if matching_network?(ni, net_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false if result
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable_network?
|
||||||
|
!@env.env.config.vm.network_options.compact.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Enables and assigns the host only network to the proper
|
||||||
|
# adapter on the VM, and saves the adapter.
|
||||||
|
def assign_network
|
||||||
|
@env.logger.info "Preparing host only network..."
|
||||||
|
|
||||||
|
@env.env.config.vm.network_options.compact.each do |network_options|
|
||||||
|
adapter = @env["vm"].vm.network_adapters[network_options[:adapter]]
|
||||||
|
adapter.enabled = true
|
||||||
|
adapter.attachment_type = :host_only
|
||||||
|
adapter.host_interface = network_name(network_options)
|
||||||
|
adapter.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the name of the proper host only network, or creates
|
||||||
|
# it if it does not exist. Vagrant determines if the host only
|
||||||
|
# network exists by comparing the netmask and the IP.
|
||||||
|
def network_name(net_options)
|
||||||
|
# First try to find a matching network
|
||||||
|
interfaces = VirtualBox::Global.global.host.network_interfaces
|
||||||
|
interfaces.each do |ni|
|
||||||
|
# Ignore non-host only interfaces which may also match,
|
||||||
|
# since they're not valid options.
|
||||||
|
next if ni.interface_type != :host_only
|
||||||
|
|
||||||
|
if net_options[:name]
|
||||||
|
return ni.name if net_options[:name] == ni.name
|
||||||
|
else
|
||||||
|
return ni.name if matching_network?(ni, net_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return @env.error!(:network_not_found, :name => net_options[:name]) if net_options[:name]
|
||||||
|
|
||||||
|
# One doesn't exist, create it.
|
||||||
|
@env.logger.info "Creating new host only network for environment..."
|
||||||
|
|
||||||
|
ni = interfaces.create
|
||||||
|
ni.enable_static(network_ip(net_options[:ip], net_options[:netmask]),
|
||||||
|
net_options[:netmask])
|
||||||
|
ni.name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tests if a network matches the given options by applying the
|
||||||
|
# netmask to the IP of the network and also to the IP of the
|
||||||
|
# virtual machine and see if they match.
|
||||||
|
def matching_network?(interface, net_options)
|
||||||
|
interface.network_mask == net_options[:netmask] &&
|
||||||
|
apply_netmask(interface.ip_address, interface.network_mask) ==
|
||||||
|
apply_netmask(net_options[:ip], net_options[:netmask])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Applies a netmask to an IP and returns the corresponding
|
||||||
|
# parts.
|
||||||
|
def apply_netmask(ip, netmask)
|
||||||
|
ip = split_ip(ip)
|
||||||
|
netmask = split_ip(netmask)
|
||||||
|
|
||||||
|
ip.map do |part|
|
||||||
|
part & netmask.shift
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Splits an IP and converts each portion into an int.
|
||||||
|
def split_ip(ip)
|
||||||
|
ip.split(".").map do |i|
|
||||||
|
i.to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a "network IP" which is a "good choice" for the IP
|
||||||
|
# for the actual network based on the netmask.
|
||||||
|
def network_ip(ip, netmask)
|
||||||
|
parts = apply_netmask(ip, netmask)
|
||||||
|
parts[3] += 1;
|
||||||
|
parts.join(".")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,246 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
|
||||||
|
|
||||||
|
class NetworkVMActionTest < Test::Unit::TestCase
|
||||||
|
setup do
|
||||||
|
@klass = Vagrant::Action::VM::Network
|
||||||
|
@app, @env = mock_action_data
|
||||||
|
|
||||||
|
@vm = mock("vm")
|
||||||
|
@vm.stubs(:name).returns("foo")
|
||||||
|
@vm.stubs(:ssh).returns(mock("ssh"))
|
||||||
|
@vm.stubs(:system).returns(mock("system"))
|
||||||
|
@env["vm"] = @vm
|
||||||
|
|
||||||
|
@internal_vm = mock("internal")
|
||||||
|
@vm.stubs(:vm).returns(@internal_vm)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "initializing" do
|
||||||
|
should "verify no bridge collisions for each network enabled" do
|
||||||
|
@env.env.config.vm.network("foo")
|
||||||
|
@klass.any_instance.expects(:verify_no_bridge_collision).once.with() do |options|
|
||||||
|
assert_equal "foo", options[:ip]
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
@klass.new(@app, @env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with an instance" do
|
||||||
|
setup do
|
||||||
|
@klass.any_instance.stubs(:verify_no_bridge_collision)
|
||||||
|
@instance = @klass.new(@app, @env)
|
||||||
|
|
||||||
|
@interfaces = []
|
||||||
|
VirtualBox::Global.global.host.stubs(:network_interfaces).returns(@interfaces)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mock_interface(options=nil)
|
||||||
|
options = {
|
||||||
|
:interface_type => :host_only,
|
||||||
|
:name => "foo"
|
||||||
|
}.merge(options || {})
|
||||||
|
|
||||||
|
interface = mock("interface")
|
||||||
|
options.each do |k,v|
|
||||||
|
interface.stubs(k).returns(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
@interfaces << interface
|
||||||
|
interface
|
||||||
|
end
|
||||||
|
|
||||||
|
context "calling" do
|
||||||
|
setup do
|
||||||
|
@env.env.config.vm.network("foo")
|
||||||
|
@instance.stubs(:enable_network?).returns(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "do nothing if network should not be enabled" do
|
||||||
|
@instance.expects(:assign_network).never
|
||||||
|
@app.expects(:call).with(@env).once
|
||||||
|
@vm.system.expects(:prepare_host_only_network).never
|
||||||
|
@vm.system.expects(:enable_host_only_network).never
|
||||||
|
|
||||||
|
@instance.call(@env)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "assign and enable the network if networking enabled" do
|
||||||
|
@instance.stubs(:enable_network?).returns(true)
|
||||||
|
|
||||||
|
run_seq = sequence("run")
|
||||||
|
@instance.expects(:assign_network).once.in_sequence(run_seq)
|
||||||
|
@app.expects(:call).with(@env).once.in_sequence(run_seq)
|
||||||
|
@vm.system.expects(:prepare_host_only_network).once.in_sequence(run_seq)
|
||||||
|
@vm.system.expects(:enable_host_only_network).once.in_sequence(run_seq)
|
||||||
|
|
||||||
|
@instance.call(@env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "checking if network is enabled" do
|
||||||
|
should "return true if the network options are set" do
|
||||||
|
@env.env.config.vm.network("foo")
|
||||||
|
assert @instance.enable_network?
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return false if the network was not set" do
|
||||||
|
assert !@instance.enable_network?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "assigning the network" do
|
||||||
|
setup do
|
||||||
|
@network_name = "foo"
|
||||||
|
@instance.stubs(:network_name).returns(@network_name)
|
||||||
|
|
||||||
|
@network_adapters = []
|
||||||
|
@internal_vm.stubs(:network_adapters).returns(@network_adapters)
|
||||||
|
|
||||||
|
@options = {
|
||||||
|
:ip => "foo",
|
||||||
|
:adapter => 7
|
||||||
|
}
|
||||||
|
|
||||||
|
@env.env.config.vm.network(@options[:ip], @options)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "setup the specified network adapter" do
|
||||||
|
adapter = mock("adapter")
|
||||||
|
@network_adapters[@options[:adapter]] = adapter
|
||||||
|
|
||||||
|
adapter.expects(:enabled=).with(true).once
|
||||||
|
adapter.expects(:attachment_type=).with(:host_only).once
|
||||||
|
adapter.expects(:host_interface=).with(@network_name).once
|
||||||
|
adapter.expects(:save).once
|
||||||
|
|
||||||
|
@instance.assign_network
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "network name" do
|
||||||
|
setup do
|
||||||
|
@instance.stubs(:matching_network?).returns(false)
|
||||||
|
|
||||||
|
@options = { :ip => :foo, :netmask => :bar, :name => nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return the network which matches" do
|
||||||
|
result = mock("result")
|
||||||
|
interface = mock_interface(:name => result)
|
||||||
|
|
||||||
|
@instance.expects(:matching_network?).with(interface, @options).returns(true)
|
||||||
|
assert_equal result, @instance.network_name(@options)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "ignore non-host only interfaces" do
|
||||||
|
@options[:name] = "foo"
|
||||||
|
mock_interface(:name => @options[:name],
|
||||||
|
:interface_type => :bridged)
|
||||||
|
|
||||||
|
@instance.network_name(@options)
|
||||||
|
assert @env.error?
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return the network which matches the name if given" do
|
||||||
|
@options[:name] = "foo"
|
||||||
|
|
||||||
|
interface = mock_interface(:name => @options[:name])
|
||||||
|
assert_equal @options[:name], @instance.network_name(@options)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "error and exit if the given network name is not found" do
|
||||||
|
@options[:name] = "foo"
|
||||||
|
|
||||||
|
@interfaces.expects(:create).never
|
||||||
|
@instance.network_name(@options)
|
||||||
|
assert @env.error?
|
||||||
|
assert_equal :network_not_found, @env.error.first
|
||||||
|
end
|
||||||
|
|
||||||
|
should "create a network for the IP and netmask" do
|
||||||
|
result = mock("result")
|
||||||
|
network_ip = :foo
|
||||||
|
|
||||||
|
interface = mock_interface(:name => result)
|
||||||
|
interface.expects(:enable_static).with(network_ip, @options[:netmask])
|
||||||
|
@interfaces.expects(:create).returns(interface)
|
||||||
|
@instance.expects(:network_ip).with(@options[:ip], @options[:netmask]).once.returns(network_ip)
|
||||||
|
|
||||||
|
assert_equal result, @instance.network_name(@options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "checking for a matching network" do
|
||||||
|
setup do
|
||||||
|
@interface = mock("interface")
|
||||||
|
@interface.stubs(:network_mask).returns("foo")
|
||||||
|
@interface.stubs(:ip_address).returns("192.168.0.1")
|
||||||
|
|
||||||
|
@options = {
|
||||||
|
:netmask => "foo",
|
||||||
|
:ip => "baz"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return false if the netmasks don't match" do
|
||||||
|
@options[:netmask] = "bar"
|
||||||
|
assert @interface.network_mask != @options[:netmask] # sanity
|
||||||
|
assert !@instance.matching_network?(@interface, @options)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return true if the netmasks yield the same IP" do
|
||||||
|
tests = [["255.255.255.0", "192.168.0.1", "192.168.0.45"],
|
||||||
|
["255.255.0.0", "192.168.45.1", "192.168.28.7"]]
|
||||||
|
|
||||||
|
tests.each do |netmask, interface_ip, guest_ip|
|
||||||
|
@options[:netmask] = netmask
|
||||||
|
@options[:ip] = guest_ip
|
||||||
|
@interface.stubs(:network_mask).returns(netmask)
|
||||||
|
@interface.stubs(:ip_address).returns(interface_ip)
|
||||||
|
|
||||||
|
assert @instance.matching_network?(@interface, @options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "applying the netmask" do
|
||||||
|
should "return the proper result" do
|
||||||
|
tests = {
|
||||||
|
["192.168.0.1","255.255.255.0"] => [192,168,0,0],
|
||||||
|
["192.168.45.10","255.255.255.0"] => [192,168,45,0]
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.each do |k,v|
|
||||||
|
assert_equal v, @instance.apply_netmask(*k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "splitting an IP" do
|
||||||
|
should "return the proper result" do
|
||||||
|
tests = {
|
||||||
|
"192.168.0.1" => [192,168,0,1]
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.each do |k,v|
|
||||||
|
assert_equal v, @instance.split_ip(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "network IP" do
|
||||||
|
should "return the proper result" do
|
||||||
|
tests = {
|
||||||
|
["192.168.0.45", "255.255.255.0"] => "192.168.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.each do |args, result|
|
||||||
|
assert_equal result, @instance.network_ip(*args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue