diff --git a/README.md b/README.md index 248b7a8..a81ef7f 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ Highly experimental Linux Containers support for Vagrant 1.1. ## Dependencies -Vagrant >= 1.1.0, the `lxc` package and a Kernel [higher than 3.5.0-17.28](#help-im-unable-to-restart-containers), +Vagrant >= 1.1.0, `lxc` and `redir` packages and a Kernel [higher than 3.5.0-17.28](#help-im-unable-to-restart-containers), which on Ubuntu 12.10 means something like: ``` sudo apt-get update && sudo apt-get dist-upgrade -sudo apt-get install lxc +sudo apt-get install lxc redir wget "http://files.vagrantup.com/packages/67bd4d30f7dbefa7c0abc643599f0244986c38c8/vagrant_`uname -m`.deb" -O /tmp/vagrant.deb sudo dpkg -i /tmp/vagrant.deb ``` @@ -22,6 +22,7 @@ sudo dpkg -i /tmp/vagrant.deb * Shared folders * Provisioning * Setting container's host name +* Port forwarding *Please refer to the [closed issues](https://github.com/fgrehm/vagrant-lxc/issues?labels=&milestone=&page=1&state=closed) for the most up to date list.* @@ -29,7 +30,7 @@ for the most up to date list.* ## Current limitations -* Port forwarding does not work [yet](https://github.com/fgrehm/vagrant-lxc/issues/6) +* Port forwarding collision detection * A hell lot of `sudo`s * Only a [single ubuntu box supported](boxes), I'm still [figuring out what should go on the .box file](https://github.com/fgrehm/vagrant-lxc/issues/4) diff --git a/development/site.pp b/development/site.pp index adc5310..5b88303 100644 --- a/development/site.pp +++ b/development/site.pp @@ -20,7 +20,8 @@ exec { # Install dependencies package { - [ 'libffi-dev', 'bsdtar', 'exuberant-ctags', 'ruby1.9.1-dev', 'htop', 'git', 'build-essential' ]: + [ 'libffi-dev', 'bsdtar', 'exuberant-ctags', 'ruby1.9.1-dev', 'htop', 'git', + 'build-essential', 'redir' ]: ensure => 'installed' ; diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 88cd6e6..eda4bcd 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -5,11 +5,13 @@ require 'vagrant-lxc/action/base_action' require 'vagrant-lxc/action/boot' require 'vagrant-lxc/action/check_created' require 'vagrant-lxc/action/check_running' +require 'vagrant-lxc/action/clear_forwarded_ports' require 'vagrant-lxc/action/create' require 'vagrant-lxc/action/created' require 'vagrant-lxc/action/destroy' require 'vagrant-lxc/action/disconnect' require 'vagrant-lxc/action/forced_halt' +require 'vagrant-lxc/action/forward_ports' require 'vagrant-lxc/action/handle_box_metadata' require 'vagrant-lxc/action/is_running' require 'vagrant-lxc/action/network' @@ -55,10 +57,10 @@ module Vagrant # b.use ClearSharedFolders b.use ShareFolders b.use Network - # b.use ForwardPorts b.use Vagrant::Action::Builtin::SetHostname # b.use SaneDefaults # b.use Customize + b.use ForwardPorts b.use Boot end end @@ -131,6 +133,7 @@ module Vagrant if env[:result] # TODO: If vagrant >=... b2.use Disconnect + b2.use ClearForwardedPorts b2.use Vagrant::Action::Builtin::Call, Vagrant::Action::Builtin::GracefulHalt, :stopped, :running do |env2, b3| if !env2[:result] && env2[:machine].provider.state.running? b3.use ForcedHalt diff --git a/lib/vagrant-lxc/action/clear_forwarded_ports.rb b/lib/vagrant-lxc/action/clear_forwarded_ports.rb new file mode 100644 index 0000000..44c0586 --- /dev/null +++ b/lib/vagrant-lxc/action/clear_forwarded_ports.rb @@ -0,0 +1,47 @@ +module Vagrant + module LXC + module Action + class ClearForwardedPorts + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::lxc::action::clear_forwarded_ports") + end + + def call(env) + @env = env + + if redir_pids.any? + env[:ui].info I18n.t("vagrant.actions.vm.clear_forward_ports.deleting") + redir_pids.each do |pid| + next unless is_redir_pid?(pid) + @logger.debug "Killing pid #{pid}" + system "sudo pkill -9 -P #{pid}" + end + + remove_redir_pids + end + + @app.call env + end + + protected + + def redir_pids + @redir_pids = Dir[@env[:machine].data_dir.join('pids').to_s + "/redir_*.pid"].map do |file| + File.read(file).strip.chomp + end + end + + def is_redir_pid?(pid) + `ps -o cmd= #{pid}`.strip.chomp =~ /redir/ + end + + def remove_redir_pids + Dir[@env[:machine].data_dir.join('pids').to_s + "/redir_*.pid"].each do |file| + File.delete file + end + end + end + end + end +end diff --git a/lib/vagrant-lxc/action/forward_ports.rb b/lib/vagrant-lxc/action/forward_ports.rb new file mode 100644 index 0000000..892ae00 --- /dev/null +++ b/lib/vagrant-lxc/action/forward_ports.rb @@ -0,0 +1,83 @@ +module Vagrant + module LXC + module Action + class ForwardPorts + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::lxc::action::forward_ports") + end + + def call(env) + @env = env + + # Continue, we need the VM to be booted in order to grab its IP + @app.call env + + # Get the ports we're forwarding + env[:forwarded_ports] = compile_forwarded_ports(env[:machine].config) + + # Warn if we're port forwarding to any privileged ports + env[:forwarded_ports].each do |fp| + if fp[:host] <= 1024 + env[:ui].warn I18n.t("vagrant.actions.vm.forward_ports.privileged_ports") + break + end + end + + env[:ui].info I18n.t("vagrant.actions.vm.forward_ports.forwarding") + forward_ports + end + + def forward_ports + @container_ip = @env[:machine].provider.container.assigned_ip + + @env[:forwarded_ports].each do |fp| + message_attributes = { + # TODO: Add support for multiple adapters + :adapter => 'eth0', + :guest_port => fp[:guest], + :host_port => fp[:host] + } + + # TODO: Remove adapter from logging + @env[:ui].info(I18n.t("vagrant.actions.vm.forward_ports.forwarding_entry", + message_attributes)) + + redir_pid = redirect_port(fp[:host], fp[:guest]) + store_redir_pid(fp[:host], redir_pid) + end + end + + private + + def compile_forwarded_ports(config) + mappings = {} + + config.vm.networks.each do |type, options| + if type == :forwarded_port + mappings[options[:host]] = options + end + end + + mappings.values + end + + def redirect_port(host, guest) + redir_cmd = "sudo redir --laddr=127.0.0.1 --lport=#{host} --cport=#{guest} --caddr=#{@container_ip}" + + @logger.debug "Forwarding port with `#{redir_cmd}`" + fork { exec redir_cmd } + end + + def store_redir_pid(host_port, redir_pid) + data_dir = @env[:machine].data_dir.join('pids') + data_dir.mkdir unless data_dir.directory? + + data_dir.join("redir_#{host_port}.pid").open('w') do |pid_file| + pid_file.write(redir_pid) + end + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 472fb04..47b184a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -7,6 +7,8 @@ end require 'bundler/setup' +require 'i18n' + require 'rspec-spies' Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f } diff --git a/spec/unit/action/clear_forwarded_ports_spec.rb b/spec/unit/action/clear_forwarded_ports_spec.rb new file mode 100644 index 0000000..4dcc997 --- /dev/null +++ b/spec/unit/action/clear_forwarded_ports_spec.rb @@ -0,0 +1,42 @@ +require 'unit_helper' + +require 'vagrant-lxc/action/clear_forwarded_ports' + +describe Vagrant::LXC::Action::ClearForwardedPorts do + let(:app) { mock(:app, call: true) } + let(:env) { {machine: machine, ui: stub(info: true)} } + let(:machine) { mock(:machine, data_dir: data_dir) } + let!(:data_dir) { Pathname.new(Dir.mktmpdir) } + let(:pids_dir) { data_dir.join('pids') } + let(:pid) { 'a-pid' } + let(:pid_cmd) { 'redir' } + + subject { described_class.new(app, env) } + + before do + pids_dir.mkdir + pids_dir.join('redir_1234.pid').open('w') { |f| f.write(pid) } + subject.stub(system: true, :` => pid_cmd) + subject.call(env) + end + + after { FileUtils.rm_rf data_dir.to_s } + + it 'removes all files under pid directory' do + Dir[pids_dir.to_s + "/redir_*.pid"].should be_empty + end + + context 'with a valid redir pid' do + it 'kills known processes' do + subject.should have_received(:system).with("sudo pkill -9 -P #{pid}") + end + end + + context 'with an invalid pid' do + let(:pid_cmd) { 'sudo ls' } + + it 'does not kill the process' do + subject.should_not have_received(:system).with("sudo pkill -9 -P #{pid}") + end + end +end diff --git a/spec/unit/action/forward_ports_spec.rb b/spec/unit/action/forward_ports_spec.rb new file mode 100644 index 0000000..0b24296 --- /dev/null +++ b/spec/unit/action/forward_ports_spec.rb @@ -0,0 +1,41 @@ +require 'unit_helper' + +require 'vagrant-lxc/action/forward_ports' + +describe Vagrant::LXC::Action::ForwardPorts do + let(:app) { mock(:app, call: true) } + let(:env) { {machine: machine, ui: stub(info: true)} } + let(:machine) { mock(:machine) } + let!(:data_dir) { Pathname.new(Dir.mktmpdir) } + let(:networks) { [[:other_config, {}], [:forwarded_port, {guest: guest_port, host: host_port}]] } + let(:host_port) { 8080 } + let(:guest_port) { 80 } + let(:provider) { fire_double('Vagrant::LXC::Provider', container: container) } + let(:container) { fire_double('Vagrant::LXC::Container', assigned_ip: container_ip) } + let(:container_ip) { '10.0.1.234' } + let(:pid) { 'a-pid' } + + subject { described_class.new(app, env) } + + before do + machine.stub_chain(:config, :vm, :networks).and_return(networks) + machine.stub(provider: provider, data_dir: data_dir) + + subject.stub(exec: true) + subject.stub(:fork) { |&block| block.call; pid } + subject.call(env) + end + + after { FileUtils.rm_rf data_dir.to_s } + + it 'forwards ports using redir' do + subject.should have_received(:exec).with( + "sudo redir --laddr=127.0.0.1 --lport=#{host_port} --cport=#{guest_port} --caddr=#{container_ip}" + ) + end + + it "stores redir pids on machine's data dir" do + pid_file = data_dir.join('pids', "redir_#{host_port}.pid").read + pid_file.should == pid + end +end