Vagrant uses correct NATForwarded port for 3.2.x

This commit is contained in:
Mitchell Hashimoto 2010-06-17 22:17:58 -07:00
parent 7691b28c53
commit 26b837f427
3 changed files with 248 additions and 79 deletions

View File

@ -11,22 +11,10 @@ module Vagrant
# report the collisions detected or will attempt to fix them
# automatically if the port is configured to do so.
def external_collision_check
# Flatten all the already-created forwarded ports into a
# flat list.
used_ports = VirtualBox::VM.all.collect do |vm|
if vm.running? && vm.uuid != runner.uuid
vm.forwarded_ports.collect do |fp|
fp.hostport.to_s
end
end
end
used_ports.flatten!
used_ports.uniq!
existing = used_ports
runner.env.config.vm.forwarded_ports.each do |name, options|
if used_ports.include?(options[:hostport].to_s)
handle_collision(name, options, used_ports)
if existing.include?(options[:hostport].to_s)
handle_collision(name, options, existing)
end
end
end
@ -34,7 +22,7 @@ module Vagrant
# Handles any collisions. This method will either attempt to
# fix the collision automatically or will raise an error if
# auto fixing is disabled.
def handle_collision(name, options, used_ports)
def handle_collision(name, options, existing_ports)
if !options[:auto]
# Auto fixing is disabled for this port forward, so we
# must throw an error so the user can fix it.
@ -46,7 +34,7 @@ module Vagrant
# left with available ports.
range = runner.env.config.vm.auto_port_range.to_a
range -= runner.env.config.vm.forwarded_ports.collect { |n, o| o[:hostport].to_i }
range -= used_ports
range -= existing_ports
if range.empty?
raise ActionException.new(:vm_port_auto_empty, :vm_name => @runner.name, :name => name, :options => options)
@ -55,7 +43,7 @@ module Vagrant
# Set the port up to be the first one and add that port to
# the used list.
options[:hostport] = range.shift
used_ports << options[:hostport]
existing_ports << options[:hostport]
# Notify the user
logger.info "Fixed port collision: #{name} now on port #{options[:hostport]}"
@ -67,10 +55,9 @@ module Vagrant
end
def clear
if runner.vm.forwarded_ports.length > 0
if used_ports.length > 0
logger.info "Deleting any previously set forwarded ports..."
fp = runner.vm.forwarded_ports.dup
fp.collect { |p| p.destroy }
clear_ports
runner.reload!
end
end
@ -78,28 +65,90 @@ module Vagrant
def forward_ports
logger.info "Forwarding ports..."
@runner.env.config.vm.forwarded_ports.each do |name, options|
runner.env.config.vm.forwarded_ports.each do |name, options|
adapter = options[:adapter]
# Assuming the only reason to establish port forwarding is because the VM is using Virtualbox NAT networking.
# Host-only or Bridged networking don't require port-forwarding and establishing forwarded ports on these
# attachment types has uncertain behaviour.
if @runner.vm.network_adapters[adapter].attachment_type == :nat
logger.info "Forwarding \"#{name}\": #{options[:guestport]} on adapter \##{adapter+1} => #{options[:hostport]}"
port = VirtualBox::ForwardedPort.new
port.name = name
port.hostport = options[:hostport]
port.guestport = options[:guestport]
port.instance = adapter
@runner.vm.forwarded_ports << port
logger.info "Forwarding \"#{name}\": #{options[:guestport]} on adapter \##{adapter+1} => #{options[:hostport]}"
forward_port(name, options)
else
logger.info "VirtualBox adapter \##{adapter+1} not configured as \"NAT\"."
logger.info "Skipped port forwarding \"#{name}\": #{options[:guestport]} on adapter\##{adapter+1} => #{options[:hostport]}"
end
end
@runner.vm.save
@runner.reload!
runner.vm.save
runner.reload!
end
#------------------------------------------------------------
# VirtualBox version-specific helpers below. VirtualBox 3.2
# introduced a breaking change into the way that forwarded
# ports are handled. For backwards compatability with 3.1, we
# need this trickery.
#------------------------------------------------------------
# TODO In a future version, fix this.
# Returns an array of used ports. This method is implemented
# differently depending on the VirtualBox version, but the
# behavior is the same.
#
# @return [Array<String>]
def used_ports
result = VirtualBox::VM.all.collect do |vm|
if vm.running? && vm.uuid != runner.uuid
if VirtualBox.version =~ /^3\.1\./
# VirtualBox 3.1.x uses forwarded ports via extra-data
vm.forwarded_ports.collect do |fp|
fp.hostport.to_s
end
else
# VirtualBox 3.2.x uses forwarded ports via NAT engines
vm.network_adapters.collect do |na|
na.nat_engine.forwarded_ports.collect do |fp|
fp.hostport.to_s
end
end
end
end
end
result.flatten.uniq
end
# Deletes existing forwarded ports.
def clear_ports
if VirtualBox.version =~ /^3\.1\./
fp = runner.vm.forwarded_ports.dup
fp.each { |p| p.destroy }
else
runner.vm.network_adapters.each do |na|
na.nat_engine.forwarded_ports.dup.each do |fp|
fp.destroy
end
end
end
end
# Forwards a port.
def forward_port(name, options)
if VirtualBox.version =~ /^3\.1\./
port = VirtualBox::ForwardedPort.new
port.name = name
port.hostport = options[:hostport]
port.guestport = options[:guestport]
port.instance = options[:adapter]
runner.vm.forwarded_ports << port
else
port = VirtualBox::NATForwardedPort.new
port.name = name
port.guestport = options[:guestport]
port.hostport = options[:hostport]
runner.vm.network_adapters[options[:adapter]].nat_engine.forwarded_ports << port
end
end
end
end

View File

@ -15,18 +15,6 @@ class ForwardPortsActionTest < Test::Unit::TestCase
context "checking for colliding external ports" do
setup do
@forwarded_port = mock("forwarded_port")
@forwarded_port.stubs(:hostport)
@forwarded_ports = [@forwarded_port]
@vm = mock("vm")
@vm.stubs(:forwarded_ports).returns(@forwarded_ports)
@vm.stubs(:running?).returns(true)
@vm.stubs(:uuid).returns("foo")
@runner.stubs(:uuid).returns("bar")
vms = [@vm]
VirtualBox::VM.stubs(:all).returns(vms)
@env = mock_environment do |config|
config.vm.forwarded_ports.clear
config.vm.forward_port("ssh", 22, 2222)
@ -34,29 +22,20 @@ class ForwardPortsActionTest < Test::Unit::TestCase
@runner.stubs(:env).returns(@env)
@used_ports = []
@action.stubs(:used_ports).returns(@used_ports)
# So no exceptions are raised
@action.stubs(:handle_collision)
end
should "ignore vms which aren't running" do
@vm.expects(:running?).returns(false)
@vm.expects(:forwarded_ports).never
@action.external_collision_check
end
should "ignore vms which are equivalent to ours" do
@runner.expects(:uuid).returns(@vm.uuid)
@vm.expects(:forwarded_ports).never
@action.external_collision_check
end
should "not raise any errors if no forwarded ports collide" do
@forwarded_port.expects(:hostport).returns(80)
@used_ports << "80"
assert_nothing_raised { @action.external_collision_check }
end
should "handle the collision if it happens" do
@forwarded_port.expects(:hostport).returns(2222)
@used_ports << "2222"
@action.expects(:handle_collision).with("ssh", anything, anything).once
@action.external_collision_check
end
@ -127,24 +106,11 @@ class ForwardPortsActionTest < Test::Unit::TestCase
@vm.expects(:network_adapters).returns([network_adapter])
network_adapter.expects(:attachment_type).returns(:nat)
@runner.env.config.vm.forwarded_ports.each do |name, opts|
forwarded_ports.expects(:<<).with do |port|
assert_equal name, port.name
assert_equal opts[:hostport], port.hostport
assert_equal opts[:guestport], port.guestport
assert_equal opts[:adapter], port.instance
true
end
end
@vm.expects(:forwarded_ports).returns(forwarded_ports)
@vm.expects(:save).once
@runner.expects(:reload!).once
@action.forward_ports
end
end
context "Not forwarding ports" do
should "No port forwarding for non NAT interfaces" do
forwarded_ports = mock("forwarded_ports")
network_adapter = mock("network_adapter")
@ -158,23 +124,176 @@ class ForwardPortsActionTest < Test::Unit::TestCase
end
context "clearing forwarded ports" do
should "call destroy on all forwarded ports" do
forwarded_ports = []
5.times do |i|
port = mock("port#{i}")
port.expects(:destroy).once
forwarded_ports << port
end
setup do
@action.stubs(:used_ports).returns([:a])
@action.stubs(:clear_ports)
end
@vm.stubs(:forwarded_ports).returns(forwarded_ports)
should "call destroy on all forwarded ports" do
@action.expects(:clear_ports).once
@runner.expects(:reload!)
@action.clear
end
should "do nothing if there are no forwarded ports" do
@vm.stubs(:forwarded_ports).returns([])
@action.stubs(:used_ports).returns([])
@runner.expects(:reload!).never
@action.clear
end
end
context "getting list of used ports" do
setup do
@vms = []
VirtualBox::VM.stubs(:all).returns(@vms)
VirtualBox.stubs(:version).returns("3.1.0")
@runner.stubs(:uuid).returns(:bar)
end
def mock_vm(options={})
options = {
:running? => true,
:uuid => :foo
}.merge(options)
vm = mock("vm")
options.each do |k,v|
vm.stubs(k).returns(v)
end
vm
end
def mock_fp(hostport)
fp = mock("fp")
fp.stubs(:hostport).returns(hostport.to_s)
fp
end
should "ignore VMs which aren't running" do
@vms << mock_vm(:running? => false)
@vms[0].expects(:forwarded_ports).never
@action.used_ports
end
should "ignore VMs of the same uuid" do
@vms << mock_vm(:uuid => @runner.uuid)
@vms[0].expects(:forwarded_ports).never
@action.used_ports
end
should "return the forwarded ports for VB 3.1.x" do
VirtualBox.stubs(:version).returns("3.1.4")
fps = [mock_fp(2222), mock_fp(80)]
@vms << mock_vm(:forwarded_ports => fps)
assert_equal %W[2222 80], @action.used_ports
end
should "return the forwarded ports for VB 3.2.x" do
VirtualBox.stubs(:version).returns("3.2.4")
fps = [mock_fp(2222), mock_fp(80)]
na = mock("na")
ne = mock("ne")
na.stubs(:nat_engine).returns(ne)
ne.stubs(:forwarded_ports).returns(fps)
@vms << mock_vm(:network_adapters => [na])
assert_equal %W[2222 80], @action.used_ports
end
end
context "clearing ports" do
def mock_fp
fp = mock("fp")
fp.expects(:destroy).once
fp
end
context "in VB 3.1.x" do
setup do
VirtualBox.stubs(:version).returns("3.1.4")
@fps = []
@vm.stubs(:forwarded_ports).returns(@fps)
end
should "destroy each forwarded port" do
@fps << mock_fp
@fps << mock_fp
@action.clear_ports
end
end
context "in VB 3.2.x" do
setup do
VirtualBox.stubs(:version).returns("3.2.8")
@adapters = []
@vm.stubs(:network_adapters).returns(@adapters)
end
def mock_adapter
na = mock("adapter")
engine = mock("engine")
engine.stubs(:forwarded_ports).returns([mock_fp])
na.stubs(:nat_engine).returns(engine)
na
end
should "destroy each forwarded port" do
@adapters << mock_adapter
@adapters << mock_adapter
@action.clear_ports
end
end
end
context "forwarding ports" do
context "in VB 3.1.x" do
setup do
VirtualBox.stubs(:version).returns("3.1.4")
end
should "forward ports" do
forwarded_ports = mock("forwarded_ports")
@vm.expects(:forwarded_ports).returns(forwarded_ports)
@runner.env.config.vm.forwarded_ports.each do |name, opts|
forwarded_ports.expects(:<<).with do |port|
assert_equal name, port.name
assert_equal opts[:hostport], port.hostport
assert_equal opts[:guestport], port.guestport
assert_equal opts[:adapter], port.instance
true
end
@action.forward_port(name, opts)
end
end
end
context "in VB 3.2.x" do
setup do
VirtualBox.stubs(:version).returns("3.2.8")
end
should "forward ports" do
name, opts = @runner.env.config.vm.forwarded_ports.first
adapters = []
adapter = mock("adapter")
engine = mock("engine")
fps = mock("forwarded ports")
adapter.stubs(:nat_engine).returns(engine)
engine.stubs(:forwarded_ports).returns(fps)
fps.expects(:<<).with do |port|
assert_equal name, port.name
assert_equal opts[:hostport], port.hostport
assert_equal opts[:guestport], port.guestport
true
end
adapters[opts[:adapter]] = adapter
@vm.stubs(:network_adapters).returns(adapters)
@action.forward_port(name, opts)
end
end
end
end

View File

@ -53,6 +53,7 @@ class CommandsSSHTest < Test::Unit::TestCase
@vm = mock("vm")
@vm.stubs(:created?).returns(true)
@vm.stubs(:ssh).returns(@ssh)
@vm.stubs(:env).returns(@env)
end
should "error and exit if invalid VM given" do