Merge pull request #39 from fgrehm/port-forwarding

Port forwarding support using redir
This commit is contained in:
Fabio Rehm 2013-03-26 19:49:17 -07:00
commit ad8d8cde81
8 changed files with 225 additions and 5 deletions

View file

@ -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)

View file

@ -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'
;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -7,6 +7,8 @@ end
require 'bundler/setup'
require 'i18n'
require 'rspec-spies'
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f }

View file

@ -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

View file

@ -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