diff --git a/config/default.rb b/config/default.rb index 2b27cfb51..20b226a98 100644 --- a/config/default.rb +++ b/config/default.rb @@ -11,7 +11,7 @@ Vagrant.configure("2") do |config| config.ssh.forward_x11 = false config.ssh.shell = "bash -l" - config.vm.auto_port_range = (2200..2250) + config.vm.usable_port_range = (2200..2250) config.vm.box_url = nil config.vm.base_mac = nil config.vm.guest = :linux diff --git a/plugins/kernel_v1/config/vm.rb b/plugins/kernel_v1/config/vm.rb index 272e2b039..1c9f8b53e 100644 --- a/plugins/kernel_v1/config/vm.rb +++ b/plugins/kernel_v1/config/vm.rb @@ -78,12 +78,12 @@ module VagrantPlugins # Upgrade to a V2 configuration def upgrade(new) - new.vm.auto_port_range = self.auto_port_range if self.auto_port_range - new.vm.base_mac = self.base_mac if self.base_mac - new.vm.box = self.box if self.box - new.vm.box_url = self.box_url if self.box_url - new.vm.guest = self.guest if self.guest - new.vm.host_name = self.host_name if self.host_name + new.vm.base_mac = self.base_mac if self.base_mac + new.vm.box = self.box if self.box + new.vm.box_url = self.box_url if self.box_url + new.vm.guest = self.guest if self.guest + new.vm.host_name = self.host_name if self.host_name + new.vm.usable_port_range = self.auto_port_range if self.auto_port_range if self.boot_mode # Enable the GUI if the boot mode is GUI. diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 171576126..24d356332 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -11,12 +11,12 @@ module VagrantPlugins class VMConfig < Vagrant.plugin("2", :config) DEFAULT_VM_NAME = :default - attr_accessor :auto_port_range attr_accessor :base_mac attr_accessor :box attr_accessor :box_url attr_accessor :guest attr_accessor :host_name + attr_accessor :usable_port_range attr_reader :forwarded_ports attr_reader :shared_folders attr_reader :networks diff --git a/plugins/providers/virtualbox/action/check_port_collisions.rb b/plugins/providers/virtualbox/action/check_port_collisions.rb index 75ed07132..12731f1ad 100644 --- a/plugins/providers/virtualbox/action/check_port_collisions.rb +++ b/plugins/providers/virtualbox/action/check_port_collisions.rb @@ -1,9 +1,12 @@ +require "set" + require "vagrant/util/is_port_open" module VagrantPlugins module ProviderVirtualBox module Action class CheckPortCollisions + include Util::CompileForwardedPorts include Vagrant::Util::IsPortOpen def initialize(app, env) @@ -14,6 +17,17 @@ module VagrantPlugins # For the handlers... @env = env + # If we don't have forwarded ports set on the environment, then + # we compile them. + env[:forwarded_ports] ||= compile_forwarded_ports(env[:machine].config) + + existing = env[:machine].provider.driver.read_used_ports + + # Calculate the auto-correct port range + @usable_ports = Set.new(env[:machine].config.vm.usable_port_range) + @usable_ports.subtract(env[:forwarded_ports].collect { |fp| fp.host_port }) + @usable_ports.subtract(existing) + # Figure out how we handle port collisions. By default we error. handler = env[:port_collision_handler] || :error @@ -24,16 +38,15 @@ module VagrantPlugins current[name] = hostport.to_i end - existing = env[:machine].provider.driver.read_used_ports - env[:machine].config.vm.forwarded_ports.each do |options| + env[:forwarded_ports].each do |fp| # Use the proper port, whether that be the configured port or the # port that is currently on use of the VM. - hostport = options[:hostport].to_i - hostport = current[options[:name]] if current.has_key?(options[:name]) + host_port = fp.host_port + host_port = current[fp.id] if current.has_key?(fp.id) - if existing.include?(hostport) || is_port_open?("127.0.0.1", hostport) + if existing.include?(host_port) || is_port_open?("127.0.0.1", host_port) # We have a collision! Handle it - send("handle_#{handler}".to_sym, options, existing) + send("handle_#{handler}".to_sym, fp, existing) end end @@ -41,47 +54,41 @@ module VagrantPlugins end # Handles a port collision by raising an exception. - def handle_error(options, existing_ports) + def handle_error(fp, existing_ports) raise Vagrant::Errors::ForwardPortCollisionResume end # Handles a port collision by attempting to fix it. - def handle_correct(options, existing_ports) + def handle_correct(fp, existing_ports) # We need to keep this for messaging purposes - original_hostport = options[:hostport] + original_hostport = fp.host_port - if !options[:auto] + if !fp.auto_correct # Auto fixing is disabled for this port forward, so we # must throw an error so the user can fix it. raise Vagrant::Errors::ForwardPortCollision, - :host_port => options[:hostport].to_s, - :guest_port => options[:guestport].to_s + :host_port => fp.host_port.to_s, + :guest_port => fp.guest_port.to_s end - # Get the auto port range and get rid of the used ports and - # ports which are being used in other forwards so we're just - # left with available ports. - range = @env[:machine].config.vm.auto_port_range.to_a - range -= @env[:machine].config.vm.forwarded_ports.collect { |opts| opts[:hostport].to_i } - range -= existing_ports - - if range.empty? + if @usable_ports.empty? raise Vagrant::Errors::ForwardPortAutolistEmpty, :vm_name => @env[:machine].name, - :host_port => options[:hostport].to_s, - :guest_port => options[:guestport].to_s + :host_port => fp.host_port.to_s, + :guest_port => fp.guest_port.to_s end - # Set the port up to be the first one and add that port to - # the used list. - options[:hostport] = range.shift - existing_ports << options[:hostport] + # Get the first usable port and set it up + new_port = @usable_ports.to_a.sort[0] + @usable_ports.delete(new_port) + fp.correct_host_port(new_port) + existing_ports << new_port # Notify the user @env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.fixed_collision", :host_port => original_hostport.to_s, - :guest_port => options[:guestport].to_s, - :new_port => options[:hostport])) + :guest_port => fp.guest_port.to_s, + :new_port => fp.host_port.to_s)) end end end diff --git a/plugins/providers/virtualbox/model/forwarded_port.rb b/plugins/providers/virtualbox/model/forwarded_port.rb index d3f759504..8cec67969 100644 --- a/plugins/providers/virtualbox/model/forwarded_port.rb +++ b/plugins/providers/virtualbox/model/forwarded_port.rb @@ -9,6 +9,11 @@ module VagrantPlugins # @return [Integer] attr_reader :adapter + # If true, this port should be auto-corrected. + # + # @return [Boolean] + attr_reader :auto_correct + # The unique ID for the forwarded port. # # @return [String] @@ -35,9 +40,18 @@ module VagrantPlugins @host_port = host_port options ||= {} + @auto_correct = true + @auto_correct = options[:auto_correct] if options.has_key?(:auto_correct) @adapter = options[:adapter] || 1 @protocol = options[:protocol] || "tcp" end + + # This corrects the host port and changes it to the given new port. + # + # @param [Integer] new_port The new port + def correct_host_port(new_port) + @host_port = new_port + end end end end diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 7a0c7fb6e..5d8bcd6f1 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -34,5 +34,9 @@ module VagrantPlugins module Model autoload :ForwardedPort, File.expand_path("../model/forwarded_port", __FILE__) end + + module Util + autoload :CompileForwardedPorts, File.expand_path("../util/compile_forwarded_ports", __FILE__) + end end end diff --git a/plugins/providers/virtualbox/util/compile_forwarded_ports.rb b/plugins/providers/virtualbox/util/compile_forwarded_ports.rb new file mode 100644 index 000000000..fb8953f56 --- /dev/null +++ b/plugins/providers/virtualbox/util/compile_forwarded_ports.rb @@ -0,0 +1,28 @@ +module VagrantPlugins + module ProviderVirtualBox + module Util + module CompileForwardedPorts + # This method compiles the forwarded ports into {ForwardedPort} + # models. + def compile_forwarded_ports(config) + mappings = {} + + config.vm.networks.each do |type, args| + if type == :forwarded_port + guest_port = args[0] + host_port = args[1] + options = args[2] || {} + id = options[:id] || + "#{guest_port.to_s(32)}-#{host_port.to_s(32)}" + + mappings[host_port] = + Model::ForwardedPort.new(id, host_port, guest_port, options) + end + end + + mappings.values + end + end + end + end +end