From 8b8bfe739baa05f9b1413000adcf812f40020bd8 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Fri, 1 Mar 2013 20:45:13 -0300 Subject: [PATCH 01/25] lxc-create: check --- lib/vagrant-lxc/actions.rb | 3 +- lib/vagrant-lxc/container.rb | 72 +++++++++++++++++++++++++++++++++++- lib/vagrant-lxc/errors.rb | 9 +++++ locales/en.yml | 5 +++ spec/unit/container_spec.rb | 30 +++++++++++++++ 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 lib/vagrant-lxc/errors.rb create mode 100644 spec/unit/container_spec.rb diff --git a/lib/vagrant-lxc/actions.rb b/lib/vagrant-lxc/actions.rb index 17d1a2f..dc9f6db 100644 --- a/lib/vagrant-lxc/actions.rb +++ b/lib/vagrant-lxc/actions.rb @@ -166,8 +166,7 @@ module Vagrant class Create < BaseAction def call(env) puts "TODO: Create container" - env[:machine].id = 'TODO-set-a-proper-machine-id' unless env[:machine].id - env[:machine].provider.container.create + env[:machine].id = env[:machine].provider.container.create @app.call env end end diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 3c12316..71a8190 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -1,14 +1,29 @@ +# FIXME: Ruby 1.8 users dont have SecureRandom +require 'securerandom' + +require 'vagrant/util/retryable' +require 'vagrant/util/subprocess' + +require "vagrant-lxc/errors" + module Vagrant module LXC class Container + # Include this so we can use `Subprocess` more easily. + include Vagrant::Util::Retryable + CONTAINER_STATE_FILE_PATH = '/tmp/vagrant-lxc-container-state-%s' def initialize(machine) @machine = machine + @logger = Log4r::Logger.new("vagrant::provider::lxc::container") end def create - puts 'TODO: Create container' + # FIXME: Ruby 1.8 users dont have SecureRandom + machine_id = SecureRandom.hex(6) + log, status = lxc(:create, {'--template' => 'ubuntu-cloud', '--name' => machine_id}, {'-S' => '/home/vagrant/.ssh/id_rsa.pub'}) + machine_id end def start @@ -32,6 +47,14 @@ module Vagrant private + def lxc(command, params, extra = {}) + params = params.map { |opt, val| "#{opt}='#{val}'" } + params << '--' if extra.any? + # Handles extra options passed to templates when using lxc-create + params << extra.map { |opt, val| "#{opt} #{val}" } + execute('sudo', "lxc-#{command}", *params.flatten) + end + def update!(state) File.open(state_file_path, 'w') { |f| f.print state } end @@ -47,6 +70,53 @@ module Vagrant def state_file_path CONTAINER_STATE_FILE_PATH % {id: @machine.id} end + + # TODO: Review code below this line, it was pretty much a copy and paste from VirtualBox base driver + def execute(*command, &block) + # Get the options hash if it exists + opts = {} + opts = command.pop if command.last.is_a?(Hash) + + tries = 0 + tries = 3 if opts[:retryable] + + # Variable to store our execution result + r = nil + + retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => 1) do + # Execute the command + r = raw(*command, &block) + + # If the command was a failure, then raise an exception that is + # nicely handled by Vagrant. + if r.exit_code != 0 + if @interrupted + @logger.info("Exit code != 0, but interrupted. Ignoring.") + else + raise LXC::Errors::ExecuteError, :command => command.inspect + end + end + end + + # Return the output, making sure to replace any Windows-style + # newlines with Unix-style. + r.stdout.gsub("\r\n", "\n") + end + + # Executes a command and returns the raw result object. + def raw(*command, &block) + int_callback = lambda do + @interrupted = true + @logger.info("Interrupted.") + end + + # Append in the options for subprocess + command << { :notify => [:stdout, :stderr] } + + Vagrant::Util::Busy.busy(int_callback) do + Vagrant::Util::Subprocess.execute(*command, &block) + end + end end end end diff --git a/lib/vagrant-lxc/errors.rb b/lib/vagrant-lxc/errors.rb new file mode 100644 index 0000000..8d0057b --- /dev/null +++ b/lib/vagrant-lxc/errors.rb @@ -0,0 +1,9 @@ +module Vagrant + module LXC + module Errors + class ExecuteError < Vagrant::Errors::VagrantError + error_key(:lxc_execute_error) + end + end + end +end diff --git a/locales/en.yml b/locales/en.yml index 1495d7f..5b9b88e 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -1,3 +1,8 @@ en: vagrant: errors: + lxc_execute_error: |- + There was an error executing %{command} + + For more information on the failure, enable detailed logging by setting + the environment variable VAGRANT_LOG to DEBUG. diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb new file mode 100644 index 0000000..63fafed --- /dev/null +++ b/spec/unit/container_spec.rb @@ -0,0 +1,30 @@ +require 'unit_helper' + +require 'vagrant-lxc/container' + +describe Vagrant::LXC::Container do + let(:machine) { fire_double('Vagrant::Machine') } + + subject { described_class.new(machine) } + + describe 'create' do + let(:last_command) { @last_command } + let(:new_machine_id) { 'random-machine-id' } + + before do + Vagrant::Util::Subprocess.stub(:execute) do |*cmds| + cmds.pop if cmds.last.is_a?(Hash) + @last_command = cmds.join(' ') + mock(exit_code: 0, stdout: '') + end + SecureRandom.stub(hex: new_machine_id) + subject.create + end + + it 'runs lxc-create with the right arguments' do + last_command.should include "--name='#{new_machine_id}'" + last_command.should include "--template='ubuntu-cloud'" + last_command.should =~ /\-\- \-S (\w|\/|\.)+\/id_rsa\.pub/ + end + end +end From e9a5385c02f71e5aa3905c66e97fd0b36c5f3882 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Fri, 1 Mar 2013 22:47:02 -0300 Subject: [PATCH 02/25] lxc-start: check --- lib/vagrant-lxc/container.rb | 20 +++---------- spec/unit/container_spec.rb | 56 +++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 71a8190..be9ff21 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -22,13 +22,12 @@ module Vagrant def create # FIXME: Ruby 1.8 users dont have SecureRandom machine_id = SecureRandom.hex(6) - log, status = lxc(:create, {'--template' => 'ubuntu-cloud', '--name' => machine_id}, {'-S' => '/home/vagrant/.ssh/id_rsa.pub'}) + log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', machine_id, '--', '-S', '/home/vagrant/.ssh/id_rsa.pub' machine_id end def start - puts 'TODO: Start container' - update!(:running) + lxc :start, '-d', '--name', @machine.id end def halt @@ -40,19 +39,8 @@ module Vagrant File.delete(state_file_path) if state_file_path end - def state - # TODO: Grab the real machine state here - read_state_from_file - end - - private - - def lxc(command, params, extra = {}) - params = params.map { |opt, val| "#{opt}='#{val}'" } - params << '--' if extra.any? - # Handles extra options passed to templates when using lxc-create - params << extra.map { |opt, val| "#{opt} #{val}" } - execute('sudo', "lxc-#{command}", *params.flatten) + def lxc(command, *args) + execute('sudo', "lxc-#{command}", *args) end def update!(state) diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 63fafed..1be33ec 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -3,17 +3,38 @@ require 'unit_helper' require 'vagrant-lxc/container' describe Vagrant::LXC::Container do + # Default subject and machine for specs let(:machine) { fire_double('Vagrant::Machine') } - subject { described_class.new(machine) } + describe 'lxc commands execution' do + let(:args) { @args } + + before do + subject.stub(:execute) { |*args| @args = args } + subject.lxc :command, '--state', 'RUNNING' + end + + it 'prepends sudo for execution' do + args[0].should == 'sudo' + end + + it 'uses the first argument as lxc command suffix' do + args[1].should == 'lxc-command' + end + + it 'sends remaining arguments for execution' do + args[2].should == '--state' + args[3].should == 'RUNNING' + end + end + describe 'create' do let(:last_command) { @last_command } let(:new_machine_id) { 'random-machine-id' } before do - Vagrant::Util::Subprocess.stub(:execute) do |*cmds| - cmds.pop if cmds.last.is_a?(Hash) + subject.stub(:lxc) do |*cmds| @last_command = cmds.join(' ') mock(exit_code: 0, stdout: '') end @@ -21,10 +42,31 @@ describe Vagrant::LXC::Container do subject.create end - it 'runs lxc-create with the right arguments' do - last_command.should include "--name='#{new_machine_id}'" - last_command.should include "--template='ubuntu-cloud'" - last_command.should =~ /\-\- \-S (\w|\/|\.)+\/id_rsa\.pub/ + it 'calls lxc-create with the right arguments' do + last_command.should =~ /^create/ + last_command.should include "--name #{new_machine_id}" + last_command.should include "--template ubuntu-cloud" + last_command.should =~ /\-\- \-S (\w|\/|\.)+\/id_rsa\.pub$/ + end + end + + describe 'start' do + let(:last_command) { @last_command } + let(:machine_id) { 'random-machine-id' } + let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + + before do + subject.stub(:lxc) do |*cmds| + @last_command = cmds.join(' ') + mock(exit_code: 0, stdout: '') + end + subject.start + end + + it 'calls lxc-start with the right arguments' do + last_command.should =~ /^start/ + last_command.should include "--name #{machine_id}" + last_command.should include '-d' end end end From f1623a9136f0f2aabdee512c7a4110b958efe9a9 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Fri, 1 Mar 2013 23:07:15 -0300 Subject: [PATCH 03/25] lxc-wait: check --- lib/vagrant-lxc/container.rb | 4 ++++ spec/unit/container_spec.rb | 26 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index be9ff21..a97ea8a 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -39,6 +39,10 @@ module Vagrant File.delete(state_file_path) if state_file_path end + def wait_until(state) + lxc :wait, '--name', @machine.id, '--state', state.to_s.upcase + end + def lxc(command, *args) execute('sudo', "lxc-#{command}", *args) end diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 1be33ec..8a99eff 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -23,13 +23,35 @@ describe Vagrant::LXC::Container do args[1].should == 'lxc-command' end - it 'sends remaining arguments for execution' do + it 'pass through remaining arguments' do args[2].should == '--state' args[3].should == 'RUNNING' end end - describe 'create' do + describe 'guard for container state' do + let(:last_command) { @last_command } + let(:machine_id) { 'random-machine-id' } + let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + + before do + subject.stub(:lxc) do |*cmds| + @last_command = cmds.join(' ') + mock(exit_code: 0, stdout: '') + end + subject.wait_until :running + end + + it 'runs lxc-wait with the machine id' do + last_command.should include "--name #{machine_id}" + end + + it 'runs lxc-wait with upcased state' do + last_command.should include "--state RUNNING" + end + end + + describe 'creation' do let(:last_command) { @last_command } let(:new_machine_id) { 'random-machine-id' } From 4a9d74514e0809fca956207b26778dc81c948724 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Fri, 1 Mar 2013 23:08:17 -0300 Subject: [PATCH 04/25] Waits for container to be running after "lxc-start"ing it --- lib/vagrant-lxc/container.rb | 1 + spec/unit/container_spec.rb | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index a97ea8a..9f14cc4 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -28,6 +28,7 @@ module Vagrant def start lxc :start, '-d', '--name', @machine.id + wait_until :running end def halt diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 8a99eff..e1daaee 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -15,7 +15,7 @@ describe Vagrant::LXC::Container do subject.lxc :command, '--state', 'RUNNING' end - it 'prepends sudo for execution' do + it 'prepends sudo' do args[0].should == 'sudo' end @@ -73,22 +73,20 @@ describe Vagrant::LXC::Container do end describe 'start' do - let(:last_command) { @last_command } - let(:machine_id) { 'random-machine-id' } - let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + let(:machine_id) { 'random-machine-id' } + let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } before do - subject.stub(:lxc) do |*cmds| - @last_command = cmds.join(' ') - mock(exit_code: 0, stdout: '') - end + subject.stub(lxc: true, wait_until: true) subject.start end it 'calls lxc-start with the right arguments' do - last_command.should =~ /^start/ - last_command.should include "--name #{machine_id}" - last_command.should include '-d' + subject.should have_received(:lxc).with(:start, '-d', '--name', machine.id) + end + + it 'waits for container state to be RUNNING' do + subject.should have_received(:wait_until).with(:running) end end end From 227a598917c59da5fc04ebbab73aa8c94a0a1b41 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Fri, 1 Mar 2013 23:27:08 -0300 Subject: [PATCH 05/25] Clean up specs --- lib/vagrant-lxc/container.rb | 4 +++- spec/unit/container_spec.rb | 44 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 9f14cc4..f321dd9 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -22,7 +22,9 @@ module Vagrant def create # FIXME: Ruby 1.8 users dont have SecureRandom machine_id = SecureRandom.hex(6) - log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', machine_id, '--', '-S', '/home/vagrant/.ssh/id_rsa.pub' + public_key = Vagrant.source_root.join('../keys/vagrant.pub').expand_path.to_s + log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', machine_id, '--', '-S', public_key + # TODO: Handle errors machine_id end diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index e1daaee..863349e 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -30,45 +30,41 @@ describe Vagrant::LXC::Container do end describe 'guard for container state' do - let(:last_command) { @last_command } let(:machine_id) { 'random-machine-id' } let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } before do - subject.stub(:lxc) do |*cmds| - @last_command = cmds.join(' ') - mock(exit_code: 0, stdout: '') - end + subject.stub :lxc subject.wait_until :running end - it 'runs lxc-wait with the machine id' do - last_command.should include "--name #{machine_id}" - end - - it 'runs lxc-wait with upcased state' do - last_command.should include "--state RUNNING" + it 'runs lxc-wait with the machine id and upcased state' do + subject.should have_received(:lxc).with( + :wait, + '--name', machine_id, + '--state', 'RUNNING' + ) end end describe 'creation' do - let(:last_command) { @last_command } - let(:new_machine_id) { 'random-machine-id' } + let(:new_machine_id) { 'random-machine-id' } + let(:public_key_path) { Vagrant.source_root.join('..', 'keys', 'vagrant.pub').expand_path.to_s } before do - subject.stub(:lxc) do |*cmds| - @last_command = cmds.join(' ') - mock(exit_code: 0, stdout: '') - end + subject.stub(:lxc) SecureRandom.stub(hex: new_machine_id) subject.create end it 'calls lxc-create with the right arguments' do - last_command.should =~ /^create/ - last_command.should include "--name #{new_machine_id}" - last_command.should include "--template ubuntu-cloud" - last_command.should =~ /\-\- \-S (\w|\/|\.)+\/id_rsa\.pub$/ + subject.should have_received(:lxc).with( + :create, + '--template', 'ubuntu-cloud', + '--name', new_machine_id, + '--', + '-S', public_key_path + ) end end @@ -82,7 +78,11 @@ describe Vagrant::LXC::Container do end it 'calls lxc-start with the right arguments' do - subject.should have_received(:lxc).with(:start, '-d', '--name', machine.id) + subject.should have_received(:lxc).with( + :start, + '-d', + '--name', machine.id + ) end it 'waits for container state to be RUNNING' do From ce8902574c63ba5c578d93f9b4b58a6bbff28b3e Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 00:03:48 -0300 Subject: [PATCH 06/25] Fix public key path --- lib/vagrant-lxc/container.rb | 2 +- spec/unit/container_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index f321dd9..cec7cdd 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -22,7 +22,7 @@ module Vagrant def create # FIXME: Ruby 1.8 users dont have SecureRandom machine_id = SecureRandom.hex(6) - public_key = Vagrant.source_root.join('../keys/vagrant.pub').expand_path.to_s + public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', machine_id, '--', '-S', public_key # TODO: Handle errors machine_id diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 863349e..0308d5e 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -49,7 +49,7 @@ describe Vagrant::LXC::Container do describe 'creation' do let(:new_machine_id) { 'random-machine-id' } - let(:public_key_path) { Vagrant.source_root.join('..', 'keys', 'vagrant.pub').expand_path.to_s } + let(:public_key_path) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s } before do subject.stub(:lxc) From e128b592416aebc47ef724e4877d4ecb54564168 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 00:05:10 -0300 Subject: [PATCH 07/25] Use lxc-info to determine machine state --- lib/vagrant-lxc/container.rb | 12 +++--------- spec/unit/container_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index cec7cdd..40ae6d0 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -12,8 +12,6 @@ module Vagrant # Include this so we can use `Subprocess` more easily. include Vagrant::Util::Retryable - CONTAINER_STATE_FILE_PATH = '/tmp/vagrant-lxc-container-state-%s' - def initialize(machine) @machine = machine @logger = Log4r::Logger.new("vagrant::provider::lxc::container") @@ -54,18 +52,14 @@ module Vagrant File.open(state_file_path, 'w') { |f| f.print state } end - def read_state_from_file - if File.exists?(state_file_path) - File.read(state_file_path).to_sym + def state + if lxc(:info, '--name', @machine.id) =~ /^state:[^A-Z]+([A-Z]+)$/ + $1.downcase.to_sym elsif @machine.id :unknown end end - def state_file_path - CONTAINER_STATE_FILE_PATH % {id: @machine.id} - end - # TODO: Review code below this line, it was pretty much a copy and paste from VirtualBox base driver def execute(*command, &block) # Get the options hash if it exists diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 0308d5e..7503119 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -89,4 +89,25 @@ describe Vagrant::LXC::Container do subject.should have_received(:wait_until).with(:running) end end + + describe 'state' do + let(:machine_id) { 'random-machine-id' } + let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + + before do + subject.stub(lxc: "state: STOPPED\npid: 2") + end + + it 'calls lxc-info with the right arguments' do + subject.state + subject.should have_received(:lxc).with( + :info, + '--name', machine_id + ) + end + + it 'maps the output of lxc-info status out to a symbol' do + subject.state.should == :stopped + end + end end From 8ca90f1071f09d2c3a0ca755082a427bbb593aa3 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 00:11:29 -0300 Subject: [PATCH 08/25] Use STOPPED status to identify if machine is off --- lib/vagrant-lxc/machine_state.rb | 4 ++-- spec/support/unit_example_group.rb | 1 + spec/unit/machine_state_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/vagrant-lxc/machine_state.rb b/lib/vagrant-lxc/machine_state.rb index a3bbc0d..cbc8361 100644 --- a/lib/vagrant-lxc/machine_state.rb +++ b/lib/vagrant-lxc/machine_state.rb @@ -1,7 +1,7 @@ module Vagrant module LXC class MachineState < Vagrant::MachineState - CREATED_STATES = %w( running poweroff ).map!(&:to_sym) + CREATED_STATES = %w( running stopped ).map!(&:to_sym) def initialize(state_id) short = state_id.to_s.gsub("_", " ") @@ -14,7 +14,7 @@ module Vagrant end def off? - self.id == :poweroff + self.id == :stopped end def running? diff --git a/spec/support/unit_example_group.rb b/spec/support/unit_example_group.rb index 13cd1f2..f6751a3 100644 --- a/spec/support/unit_example_group.rb +++ b/spec/support/unit_example_group.rb @@ -5,6 +5,7 @@ module UnitExampleGroup Object.any_instance.stub(:system) { |*args, &block| UnitExampleGroup.prevent_system_calls(*args, &block) } + require 'vagrant/util/subprocess' Vagrant::Util::Subprocess.stub(:execute) { |*args, &block| UnitExampleGroup.prevent_system_calls(*args, &block) } diff --git a/spec/unit/machine_state_spec.rb b/spec/unit/machine_state_spec.rb index 9865372..e1082b4 100644 --- a/spec/unit/machine_state_spec.rb +++ b/spec/unit/machine_state_spec.rb @@ -29,8 +29,8 @@ describe Vagrant::LXC::MachineState do it { should_not be_off } end - context 'when state id is :poweroff' do - subject { described_class.new(:poweroff) } + context 'when state id is :stopped' do + subject { described_class.new(:stopped) } it { should be_created } it { should be_off } From 661b00ea22e67503637a9661a384881a114b511f Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 00:55:45 -0300 Subject: [PATCH 09/25] Remove LXC::Container dependency on a vagrant machine --- lib/vagrant-lxc/container.rb | 20 ++++++++++---------- lib/vagrant-lxc/provider.rb | 2 +- spec/unit/container_spec.rb | 27 ++++++++++++--------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 40ae6d0..3db6265 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -12,22 +12,22 @@ module Vagrant # Include this so we can use `Subprocess` more easily. include Vagrant::Util::Retryable - def initialize(machine) - @machine = machine - @logger = Log4r::Logger.new("vagrant::provider::lxc::container") + def initialize(name) + @name = name + @logger = Log4r::Logger.new("vagrant::provider::lxc::container") end def create # FIXME: Ruby 1.8 users dont have SecureRandom - machine_id = SecureRandom.hex(6) + name = SecureRandom.hex(6) public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s - log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', machine_id, '--', '-S', public_key + log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', name, '--', '-S', public_key # TODO: Handle errors - machine_id + @name = name end def start - lxc :start, '-d', '--name', @machine.id + lxc :start, '-d', '--name', @name wait_until :running end @@ -41,7 +41,7 @@ module Vagrant end def wait_until(state) - lxc :wait, '--name', @machine.id, '--state', state.to_s.upcase + lxc :wait, '--name', @name, '--state', state.to_s.upcase end def lxc(command, *args) @@ -53,9 +53,9 @@ module Vagrant end def state - if lxc(:info, '--name', @machine.id) =~ /^state:[^A-Z]+([A-Z]+)$/ + if lxc(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/ $1.downcase.to_sym - elsif @machine.id + elsif @name :unknown end end diff --git a/lib/vagrant-lxc/provider.rb b/lib/vagrant-lxc/provider.rb index 9f49caf..87bb07c 100644 --- a/lib/vagrant-lxc/provider.rb +++ b/lib/vagrant-lxc/provider.rb @@ -13,7 +13,7 @@ module Vagrant def initialize(machine) @logger = Log4r::Logger.new("vagrant::provider::lxc") @machine = machine - @container = Container.new(@machine) + @container = Container.new(@machine.id) end # @see Vagrant::Plugin::V1::Provider#action diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 7503119..2550ab3 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -3,9 +3,9 @@ require 'unit_helper' require 'vagrant-lxc/container' describe Vagrant::LXC::Container do - # Default subject and machine for specs - let(:machine) { fire_double('Vagrant::Machine') } - subject { described_class.new(machine) } + # Default subject and container name for specs + let(:name) { nil } + subject { described_class.new(name) } describe 'lxc commands execution' do let(:args) { @args } @@ -30,8 +30,7 @@ describe Vagrant::LXC::Container do end describe 'guard for container state' do - let(:machine_id) { 'random-machine-id' } - let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + let(:name) { 'random-container-name' } before do subject.stub :lxc @@ -41,19 +40,19 @@ describe Vagrant::LXC::Container do it 'runs lxc-wait with the machine id and upcased state' do subject.should have_received(:lxc).with( :wait, - '--name', machine_id, + '--name', name, '--state', 'RUNNING' ) end end describe 'creation' do - let(:new_machine_id) { 'random-machine-id' } + let(:name) { 'random-container-name' } let(:public_key_path) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s } before do subject.stub(:lxc) - SecureRandom.stub(hex: new_machine_id) + SecureRandom.stub(hex: name) subject.create end @@ -61,7 +60,7 @@ describe Vagrant::LXC::Container do subject.should have_received(:lxc).with( :create, '--template', 'ubuntu-cloud', - '--name', new_machine_id, + '--name', name, '--', '-S', public_key_path ) @@ -69,8 +68,7 @@ describe Vagrant::LXC::Container do end describe 'start' do - let(:machine_id) { 'random-machine-id' } - let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + let(:name) { 'container-name' } before do subject.stub(lxc: true, wait_until: true) @@ -81,7 +79,7 @@ describe Vagrant::LXC::Container do subject.should have_received(:lxc).with( :start, '-d', - '--name', machine.id + '--name', name ) end @@ -91,8 +89,7 @@ describe Vagrant::LXC::Container do end describe 'state' do - let(:machine_id) { 'random-machine-id' } - let(:machine) { fire_double('Vagrant::Machine', id: machine_id) } + let(:name) { 'random-container-name' } before do subject.stub(lxc: "state: STOPPED\npid: 2") @@ -102,7 +99,7 @@ describe Vagrant::LXC::Container do subject.state subject.should have_received(:lxc).with( :info, - '--name', machine_id + '--name', name ) end From 5291f13316e869913f02e27bfcbff389f4670998 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 01:18:38 -0300 Subject: [PATCH 10/25] Handle stale machine id / container name --- lib/vagrant-lxc/container.rb | 12 +++++++++++- lib/vagrant-lxc/provider.rb | 28 +++++++++++++++++++++++++--- spec/unit/container_spec.rb | 29 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 3db6265..0806849 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -12,11 +12,21 @@ module Vagrant # Include this so we can use `Subprocess` more easily. include Vagrant::Util::Retryable + # This is raised if the container can't be found when initializing it with + # an UUID. + class NotFound < StandardError; end + + attr_reader :name + def initialize(name) @name = name @logger = Log4r::Logger.new("vagrant::provider::lxc::container") end + def validate! + raise NotFound if @name && ! lxc(:ls).split("\n").include?(@name) + end + def create # FIXME: Ruby 1.8 users dont have SecureRandom name = SecureRandom.hex(6) @@ -53,7 +63,7 @@ module Vagrant end def state - if lxc(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/ + if @name && lxc(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/ $1.downcase.to_sym elsif @name :unknown diff --git a/lib/vagrant-lxc/provider.rb b/lib/vagrant-lxc/provider.rb index 87bb07c..175d17e 100644 --- a/lib/vagrant-lxc/provider.rb +++ b/lib/vagrant-lxc/provider.rb @@ -6,14 +6,32 @@ require "log4r" module Vagrant module LXC - # DISCUSS: VirtualBox provider has a #machine_id_changed, do we need to handle it as well? class Provider < Vagrant.plugin("2", :provider) attr_reader :container def initialize(machine) @logger = Log4r::Logger.new("vagrant::provider::lxc") @machine = machine - @container = Container.new(@machine.id) + + machine_id_changed + end + + # If the machine ID changed, then we need to rebuild our underlying + # container. + def machine_id_changed + id = @machine.id + + begin + @logger.debug("Instantiating the container for: #{id.inspect}") + @container = Container.new(id) + @container.validate! + rescue Container::NotFound + # The container doesn't exist, so we probably have a stale + # ID. Just clear the id out of the machine and reload it. + @logger.debug("Container not found! Clearing saved machine ID and reloading.") + id = nil + retry + end end # @see Vagrant::Plugin::V1::Provider#action @@ -28,7 +46,11 @@ module Vagrant end def state - LXC::MachineState.new(@container.state) + state_id = nil + state_id = :not_created if !@container.name + state_id = @container.state if !state_id + state_id = :unknown if !state_id + LXC::MachineState.new(state_id) end def to_s diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 2550ab3..954e462 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -7,6 +7,35 @@ describe Vagrant::LXC::Container do let(:name) { nil } subject { described_class.new(name) } + describe 'container name validation' do + let(:unknown_container) { described_class.new('unknown') } + let(:valid_container) { described_class.new('valid') } + let(:new_container) { described_class.new(nil) } + + before do + unknown_container.stub(lxc: 'valid') + valid_container.stub(lxc: 'valid') + end + + it 'raises a NotFound error if an unknown container name gets provided' do + expect { + unknown_container.validate! + }.to raise_error(Vagrant::LXC::Container::NotFound) + end + + it 'does not raise a NotFound error if a valid container name gets provided' do + expect { + valid_container.validate! + }.to_not raise_error(Vagrant::LXC::Container::NotFound) + end + + it 'does not raise a NotFound error if nil is provider as name' do + expect { + new_container.validate! + }.to_not raise_error(Vagrant::LXC::Container::NotFound) + end + end + describe 'lxc commands execution' do let(:args) { @args } From da534c3b0e273677c6c694cc903cd0b8d078f7b5 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 01:20:27 -0300 Subject: [PATCH 11/25] lxc-shutdown: check --- lib/vagrant-lxc/container.rb | 3 ++- spec/unit/container_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 0806849..f292f90 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -42,7 +42,8 @@ module Vagrant end def halt - update!(:poweroff) + lxc :shutdown, '--name', @name + wait_until :stopped end def destroy diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 954e462..86c1dbc 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -117,6 +117,26 @@ describe Vagrant::LXC::Container do end end + describe 'halt' do + let(:name) { 'random-container-name' } + + before do + subject.stub(lxc: true, wait_until: true) + subject.halt + end + + it 'calls lxc-shutdown with the right arguments' do + subject.should have_received(:lxc).with( + :shutdown, + '--name', name + ) + end + + it 'waits for container state to be STOPPED' do + subject.should have_received(:wait_until).with(:stopped) + end + end + describe 'state' do let(:name) { 'random-container-name' } From b02cf9c5e8428f185b1a62f9c79e05df969aa2e1 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 01:34:13 -0300 Subject: [PATCH 12/25] Clear machine id after container destruction in order to be able to use its name with lxc-destroy --- lib/vagrant-lxc/actions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant-lxc/actions.rb b/lib/vagrant-lxc/actions.rb index dc9f6db..5ec962b 100644 --- a/lib/vagrant-lxc/actions.rb +++ b/lib/vagrant-lxc/actions.rb @@ -173,8 +173,8 @@ module Vagrant class Destroy < BaseAction def call(env) - env[:machine].id = nil env[:machine].provider.container.destroy + env[:machine].id = nil @app.call env end end From b8e5a9fa1cf02687ca1ab957bb09f1051821a42e Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 01:34:47 -0300 Subject: [PATCH 13/25] lxc-destroy: check --- lib/vagrant-lxc/container.rb | 3 +-- spec/unit/container_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index f292f90..046fe8f 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -47,8 +47,7 @@ module Vagrant end def destroy - puts "TODO: Destroy container" - File.delete(state_file_path) if state_file_path + lxc :destroy, '--name', @name end def wait_until(state) diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 86c1dbc..dc6bde8 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -96,6 +96,22 @@ describe Vagrant::LXC::Container do end end + describe 'destruction' do + let(:name) { 'container-name' } + + before do + subject.stub(lxc: true) + subject.destroy + end + + it 'calls lxc-create with the right arguments' do + subject.should have_received(:lxc).with( + :destroy, + '--name', name, + ) + end + end + describe 'start' do let(:name) { 'container-name' } From 849e9c73d77b70f5d19c77233de7da23d09a162f Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 03:57:03 -0300 Subject: [PATCH 14/25] Rename LXC::Actions to LXC::Action --- lib/vagrant-lxc/{actions.rb => action.rb} | 2 +- lib/vagrant-lxc/provider.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename lib/vagrant-lxc/{actions.rb => action.rb} (99%) diff --git a/lib/vagrant-lxc/actions.rb b/lib/vagrant-lxc/action.rb similarity index 99% rename from lib/vagrant-lxc/actions.rb rename to lib/vagrant-lxc/action.rb index 5ec962b..30f5178 100644 --- a/lib/vagrant-lxc/actions.rb +++ b/lib/vagrant-lxc/action.rb @@ -2,7 +2,7 @@ module Vagrant module LXC - module Actions + module Action # This action is responsible for reloading the machine, which # brings it down, sucks in new configuration, and brings the # machine back up with the new configuration. diff --git a/lib/vagrant-lxc/provider.rb b/lib/vagrant-lxc/provider.rb index 175d17e..783d6d0 100644 --- a/lib/vagrant-lxc/provider.rb +++ b/lib/vagrant-lxc/provider.rb @@ -1,4 +1,4 @@ -require "vagrant-lxc/actions" +require "vagrant-lxc/action" require "vagrant-lxc/container" require "vagrant-lxc/machine_state" @@ -41,7 +41,7 @@ module Vagrant # given action. action_method = "action_#{name}" # TODO: Rename to singular - return LXC::Actions.send(action_method) if LXC::Actions.respond_to?(action_method) + return LXC::Action.send(action_method) if LXC::Action.respond_to?(action_method) nil end From 79efbe9cd0d35c748c8bc6b30e0a10eae4e92c37 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 12:17:04 -0300 Subject: [PATCH 15/25] Make use of box metadata for creating containers --- lib/vagrant-lxc/action.rb | 22 +++-------- lib/vagrant-lxc/action/base_action.rb | 16 ++++++++ lib/vagrant-lxc/action/handle_box_metadata.rb | 39 +++++++++++++++++++ lib/vagrant-lxc/container.rb | 11 ++++-- spec/unit/action/handle_box_metadata_spec.rb | 34 ++++++++++++++++ spec/unit/container_spec.rb | 9 +++-- 6 files changed, 108 insertions(+), 23 deletions(-) create mode 100644 lib/vagrant-lxc/action/base_action.rb create mode 100644 lib/vagrant-lxc/action/handle_box_metadata.rb create mode 100644 spec/unit/action/handle_box_metadata_spec.rb diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 30f5178..45483b7 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -1,5 +1,7 @@ -# TODO: Split action classes into their own files +require 'vagrant-lxc/action/base_action' +require 'vagrant-lxc/action/handle_box_metadata' +# TODO: Split action classes into their own files module Vagrant module LXC module Action @@ -71,8 +73,8 @@ module Vagrant b.use Vagrant::Action::Builtin::Call, Created do |env, b2| # If the VM is NOT created yet, then do the setup steps if !env[:result] + b2.use HandleBoxMetadata b2.use Create - # We'll probably have other actions down here... end end b.use action_start @@ -129,18 +131,6 @@ module Vagrant end end - - class BaseAction - def initialize(app, env) - @app = app - end - - def call(env) - puts "TODO: Implement #{self.class.name}" - @app.call(env) - end - end - class Created < BaseAction def call(env) # Set the result to be true if the machine is created. @@ -165,8 +155,8 @@ module Vagrant class Create < BaseAction def call(env) - puts "TODO: Create container" - env[:machine].id = env[:machine].provider.container.create + machine_id = env[:machine].provider.container.create(env[:machine].box.metadata) + env[:machine].id = machine_id @app.call env end end diff --git a/lib/vagrant-lxc/action/base_action.rb b/lib/vagrant-lxc/action/base_action.rb new file mode 100644 index 0000000..8167974 --- /dev/null +++ b/lib/vagrant-lxc/action/base_action.rb @@ -0,0 +1,16 @@ +module Vagrant + module LXC + module Action + class BaseAction + def initialize(app, env) + @app = app + end + + def call(env) + puts "TODO: Implement #{self.class.name}" + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-lxc/action/handle_box_metadata.rb b/lib/vagrant-lxc/action/handle_box_metadata.rb new file mode 100644 index 0000000..5a1ca81 --- /dev/null +++ b/lib/vagrant-lxc/action/handle_box_metadata.rb @@ -0,0 +1,39 @@ +require 'vagrant-lxc/action/base_action' + +module Vagrant + module LXC + module Action + # Prepare arguments to be used for lxc-create + class HandleBoxMetadata < BaseAction + LXC_TEMPLATES_PATH = Pathname.new("/usr/share/lxc/templates") + + def initialize(app, env) + super + @logger = Log4r::Logger.new("vagrant::lxc::action::handle_box_metadata") + end + + def call(env) + box = env[:machine].box + metadata = box.metadata + template_name = metadata['template-name'] + + metadata.merge!( + 'template-name' => "vagrant-#{box.name}-#{template_name}", + 'tar-cache' => box.directory.join(metadata['tar-cache']).to_s + ) + + # Prepends "lxc-" to the template file so that `lxc-create` is able to find it + dest = LXC_TEMPLATES_PATH.join("lxc-#{metadata['template-name']}").to_s + src = box.directory.join(template_name).to_s + + @logger.debug('Copying LXC template into place') + # This should only ask for administrative permission once, even + # though its executed in multiple subshells. + system(%Q[sudo su root -c "cp #{src} #{dest}"]) + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 046fe8f..d24f765 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -27,12 +27,15 @@ module Vagrant raise NotFound if @name && ! lxc(:ls).split("\n").include?(@name) end - def create + def create(metadata = {}) # FIXME: Ruby 1.8 users dont have SecureRandom - name = SecureRandom.hex(6) - public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s - log, status = lxc :create, '--template', 'ubuntu-cloud', '--name', name, '--', '-S', public_key + # @logger.info('Creating container...') + name = SecureRandom.hex(6) + public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s + # TODO: Handle errors + lxc :create, '--template', metadata['template-name'], '--name', name, '--', '-S', public_key, '-T', metadata['tar-cache'] + @name = name end diff --git a/spec/unit/action/handle_box_metadata_spec.rb b/spec/unit/action/handle_box_metadata_spec.rb new file mode 100644 index 0000000..384479e --- /dev/null +++ b/spec/unit/action/handle_box_metadata_spec.rb @@ -0,0 +1,34 @@ +require 'unit_helper' + +require 'vagrant-lxc/action/handle_box_metadata' + +describe Vagrant::LXC::Action::HandleBoxMetadata do + let(:tar_cache) { 'template.zip' } + let(:template_name) { 'ubuntu-lts' } + let(:metadata) { {'template-name' => template_name, 'tar-cache' => tar_cache} } + let(:box) { mock(:box, name: 'box-name', metadata: metadata, directory: Pathname.new('/path/to/box')) } + let(:machine) { mock(:machine, box: box) } + let(:app) { mock(:app, call: true) } + let(:env) { {machine: machine} } + + subject { described_class.new(app, env) } + + before do + subject.stub(:system) + subject.call(env) + end + + it 'prepends box directory to tar-cache' do + metadata['tar-cache'].should == "#{box.directory.to_s}/#{tar_cache}" + end + + it 'prepends vagrant and box name to template-name' do + metadata['template-name'].should == "vagrant-#{box.name}-#{template_name}" + end + + it 'copies box template file to the right folder' do + src = box.directory.join(template_name).to_s + dest = "/usr/share/lxc/templates/lxc-#{metadata['template-name']}" + subject.should have_received(:system).with("sudo su root -c \"cp #{src} #{dest}\"") + end +end diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index dc6bde8..9dccdfd 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -77,21 +77,24 @@ describe Vagrant::LXC::Container do describe 'creation' do let(:name) { 'random-container-name' } + let(:template_name) { 'template-name' } + let(:tar_cache_path) { '/path/to/tar/cache' } let(:public_key_path) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s } before do subject.stub(:lxc) SecureRandom.stub(hex: name) - subject.create + subject.create 'template-name' => template_name, 'tar-cache' => tar_cache_path end it 'calls lxc-create with the right arguments' do subject.should have_received(:lxc).with( :create, - '--template', 'ubuntu-cloud', + '--template', template_name, '--name', name, '--', - '-S', public_key_path + '-S', public_key_path, + '-T', tar_cache_path ) end end From e1f62e4e56f02797721e07945f6ed0abb7156c04 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 12:26:19 -0300 Subject: [PATCH 16/25] Build ubuntu cloud box with the required metadata and remove dummy box --- .gitignore | 3 +- boxes/ubuntu-cloud/lxc-ubuntu-cloud | 406 ++++++++++++++++++++++++++++ boxes/ubuntu-cloud/metadata.json | 7 + dummy-box-files/metadata.json | 1 - setup-vagrant-dev-box | 11 +- tasks/boxes.rake | 10 + tasks/package_dummy_box.rake | 4 - 7 files changed, 430 insertions(+), 12 deletions(-) create mode 100755 boxes/ubuntu-cloud/lxc-ubuntu-cloud create mode 100644 boxes/ubuntu-cloud/metadata.json delete mode 100644 dummy-box-files/metadata.json create mode 100644 tasks/boxes.rake delete mode 100644 tasks/package_dummy_box.rake diff --git a/.gitignore b/.gitignore index af66537..f6f0573 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ doc/ .vagrant /cache -/dummy-ubuntu-cloudimg.box +/boxes/**/*.tar.gz +/boxes/output/ diff --git a/boxes/ubuntu-cloud/lxc-ubuntu-cloud b/boxes/ubuntu-cloud/lxc-ubuntu-cloud new file mode 100755 index 0000000..7f3e422 --- /dev/null +++ b/boxes/ubuntu-cloud/lxc-ubuntu-cloud @@ -0,0 +1,406 @@ +#!/bin/bash + +# template script for generating ubuntu container for LXC based on released cloud +# images +# +# Copyright © 2012 Serge Hallyn +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2, as +# published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +set -e + +if [ -r /etc/default/lxc ]; then + . /etc/default/lxc +fi + +copy_configuration() +{ + path=$1 + rootfs=$2 + name=$3 + arch=$4 + release=$5 + + if [ $arch = "i386" ]; then + arch="i686" + fi + + # if there is exactly one veth network entry, make sure it has an + # associated hwaddr. + nics=`grep -e '^lxc\.network\.type[ \t]*=[ \t]*veth' $path/config | wc -l` + if [ $nics -eq 1 ]; then + grep -q "^lxc.network.hwaddr" $path/config || cat <> $path/config +lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//') +EOF + fi + + grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config + cat <> $path/config +lxc.utsname = $name + +lxc.tty = 4 +lxc.pts = 1024 +lxc.mount = $path/fstab +lxc.arch = $arch +lxc.cap.drop = sys_module mac_admin +lxc.pivotdir = lxc_putold + +# uncomment the next line to run the container unconfined: +#lxc.aa_profile = unconfined + +lxc.cgroup.devices.deny = a +# Allow any mknod (but not using the node) +lxc.cgroup.devices.allow = c *:* m +lxc.cgroup.devices.allow = b *:* m +# /dev/null and zero +lxc.cgroup.devices.allow = c 1:3 rwm +lxc.cgroup.devices.allow = c 1:5 rwm +# consoles +lxc.cgroup.devices.allow = c 5:1 rwm +lxc.cgroup.devices.allow = c 5:0 rwm +#lxc.cgroup.devices.allow = c 4:0 rwm +#lxc.cgroup.devices.allow = c 4:1 rwm +# /dev/{,u}random +lxc.cgroup.devices.allow = c 1:9 rwm +lxc.cgroup.devices.allow = c 1:8 rwm +lxc.cgroup.devices.allow = c 136:* rwm +lxc.cgroup.devices.allow = c 5:2 rwm +# rtc +lxc.cgroup.devices.allow = c 254:0 rwm +#fuse +lxc.cgroup.devices.allow = c 10:229 rwm +#tun +lxc.cgroup.devices.allow = c 10:200 rwm +#full +lxc.cgroup.devices.allow = c 1:7 rwm +#hpet +lxc.cgroup.devices.allow = c 10:228 rwm +#kvm +lxc.cgroup.devices.allow = c 10:232 rwm +EOF + + cat < $path/fstab +proc proc proc nodev,noexec,nosuid 0 0 +sysfs sys sysfs defaults 0 0 +EOF + + # rmdir /dev/shm for containers that have /run/shm + # I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did + # get bind mounted to the host's /run/shm. So try to rmdir + # it, and in case that fails move it out of the way. + if [ ! -L $rootfs/dev/shm ] && [ -d $rootfs/run/shm ] && [ -e $rootfs/dev/shm ]; then + mv $rootfs/dev/shm $rootfs/dev/shm.bak + ln -s /run/shm $rootfs/dev/shm + fi + + return 0 +} + +usage() +{ + cat < ]: Release name of container, defaults to host +[ -a | --arch ]: Arhcitecture of container, defaults to host arcitecture +[ -C | --cloud ]: Configure container for use with meta-data service, defaults to no +[ -T | --tarball ]: Location of tarball +[ -d | --debug ]: Run with 'set -x' to debug errors +[ -s | --stream]: Use specified stream rather than 'released' + +Options, mutually exclusive of "-C" and "--cloud": + [ -i | --hostid ]: HostID for cloud-init, defaults to random string + [ -u | --userdata ]: Cloud-init user-data file to configure container on start + [ -S | --auth-key ]: SSH Public key file to inject into container + [ -L | --nolocales ]: Do not copy host's locales into container + +EOF + return 0 +} + +options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds:u: -l arch:,help,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata: -- "$@") +if [ $? -ne 0 ]; then + usage $(basename $0) + exit 1 +fi +eval set -- "$options" + +release=lucid +if [ -f /etc/lsb-release ]; then + . /etc/lsb-release + case "$DISTRIB_CODENAME" in + lucid|natty|oneiric|precise|quantal) + release=$DISTRIB_CODENAME + ;; + esac +fi + +arch=$(arch) + +# Code taken from debootstrap +if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then + arch=`/usr/bin/dpkg --print-architecture` +elif type udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then + arch=`/usr/bin/udpkg --print-architecture` +else + arch=$(arch) + if [ "$arch" = "i686" ]; then + arch="i386" + elif [ "$arch" = "x86_64" ]; then + arch="amd64" + elif [ "$arch" = "armv7l" ]; then + # note: arm images don't exist before oneiric; are called armhf in + # precise and later; and are not supported by the query, so we don't actually + # support them yet (see check later on). When Query2 is available, + # we'll use that to enable arm images. + arch="armel" + fi +fi + +debug=0 +hostarch=$arch +cloud=0 +locales=1 +flushcache=0 +stream="released" +while true +do + case "$1" in + -h|--help) usage $0 && exit 0;; + -p|--path) path=$2; shift 2;; + -n|--name) name=$2; shift 2;; + -F|--flush-cache) flushcache=1; shift 1;; + -r|--release) release=$2; shift 2;; + -a|--arch) arch=$2; shift 2;; + -i|--hostid) host_id=$2; shift 2;; + -u|--userdata) userdata=$2; shift 2;; + -C|--cloud) cloud=1; shift 1;; + -S|--auth-key) auth_key=$2; shift 2;; + -L|--no_locales) locales=0; shift 1;; + -T|--tarball) tarball=$2; shift 2;; + -d|--debug) debug=1; shift 1;; + -s|--stream) stream=$2; shift 2;; + --) shift 1; break ;; + *) break ;; + esac +done + +if [ $debug -eq 1 ]; then + set -x +fi + +if [ "$arch" == "i686" ]; then + arch=i386 +fi + +if [ $hostarch = "i386" -a $arch = "amd64" ]; then + echo "can't create amd64 container on i386" + exit 1 +fi + +if [ $arch != "i386" -a $arch != "amd64" ]; then + echo "Only i386 and amd64 are supported by the ubuntu cloud template." + exit 1 +fi + +if [ "$stream" != "daily" -a "$stream" != "released" ]; then + echo "Only 'daily' and 'released' streams are supported" + exit 1 +fi + +if [ -n "$userdata" ]; then + if [ ! -f "$userdata" ]; then + echo "Userdata ($userdata) does not exist" + exit 1 + else + userdata=`readlink -f $userdata` + fi +fi + +if [ -n "$auth_key" ]; then + if [ ! -f "$auth_key" ]; then + echo "--auth-key=${auth_key} must reference a file" + exit 1 + fi + auth_key=$(readlink -f "${auth_key}") || + { echo "failed to get full path for auth_key"; exit 1; } +fi + +if [ -z "$path" ]; then + echo "'path' parameter is required" + exit 1 +fi + +if [ "$(id -u)" != "0" ]; then + echo "This script should be run as 'root'" + exit 1 +fi + +# detect rootfs +config="$path/config" +if grep -q '^lxc.rootfs' $config 2>/dev/null ; then + rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'` +else + rootfs=$path/rootfs +fi + +type ubuntu-cloudimg-query +type wget + +# determine the url, tarball, and directory names +# download if needed +cache="/var/cache/lxc/cloud-$release" + +mkdir -p $cache + +if [ -n "$tarball" ]; then + url2="$tarball" +else + url1=`ubuntu-cloudimg-query $release $stream $arch --format "%{url}\n"` + url2=`echo $url1 | sed -e 's/.tar.gz/-root\0/'` +fi + +filename=`basename $url2` + +wgetcleanup() +{ + rm -f $filename +} + +buildcleanup() +{ + cd $rootfs + umount -l $cache/$xdir || true + rm -rf $cache +} + +# if the release doesn't have a *-rootfs.tar.gz, then create one from the +# cloudimg.tar.gz by extracting the .img, mounting it loopback, and creating +# a tarball from the mounted image. +build_root_tgz() +{ + url=$1 + filename=$2 + + xdir=`mktemp -d -p .` + tarname=`basename $url` + imgname="$release-*-cloudimg-$arch.img" + trap buildcleanup EXIT SIGHUP SIGINT SIGTERM + if [ $flushcache -eq 1 -o ! -f $cache/$tarname ]; then + rm -f $tarname + echo "Downloading cloud image from $url" + wget $url || { echo "Couldn't find cloud image $url."; exit 1; } + fi + echo "Creating new cached cloud image rootfs" + tar --wildcards -zxf $tarname $imgname + mount -o loop $imgname $xdir + (cd $xdir; tar zcf ../$filename .) + umount $xdir + rm -f $tarname $imgname + rmdir $xdir + echo "New cloud image cache created" + trap EXIT + trap SIGHUP + trap SIGINT + trap SIGTERM +} + +mkdir -p /var/lock/subsys/ +( + flock -x 200 + + cd $cache + if [ $flushcache -eq 1 ]; then + echo "Clearing the cached images" + rm -f $filename + fi + + trap wgetcleanup EXIT SIGHUP SIGINT SIGTERM + if [ ! -f $filename ]; then + wget $url2 || build_root_tgz $url1 $filename + fi + trap EXIT + trap SIGHUP + trap SIGINT + trap SIGTERM + + echo "Extracting container rootfs" + mkdir -p $rootfs + cd $rootfs + tar --numeric-owner -zxf $cache/$filename + + + if [ $cloud -eq 0 ]; then + echo "Configuring for running outside of a cloud environment" + echo "If you want to configure for a cloud evironment, please use '-- -C' to create the container" + + seed_d=$rootfs/var/lib/cloud/seed/nocloud-net + rhostid=$(uuidgen | cut -c -8) + host_id=${hostid:-$rhostid} + mkdir -p $seed_d + + cat > "$seed_d/meta-data" <> "$seed_d/meta-data" + [ $? -eq 0 ] || + { echo "failed to write public keys to metadata"; exit 1; } + fi + + rm $rootfs/etc/hostname + + if [ $locales -eq 1 ]; then + cp /usr/lib/locale/locale-archive $rootfs/usr/lib/locale/locale-archive + fi + + if [ -f "$userdata" ]; then + echo "Using custom user-data" + cp $userdata $seed_d/user-data + else + + if [ -z "$MIRROR" ]; then + MIRROR="http://archive.ubuntu.com/ubuntu" + fi + + cat > "$seed_d/user-data" </var/lock/subsys/lxc-ubucloud + +copy_configuration $path $rootfs $name $arch $release + +echo "Container $name created." +exit 0 + +# vi: ts=4 expandtab diff --git a/boxes/ubuntu-cloud/metadata.json b/boxes/ubuntu-cloud/metadata.json new file mode 100644 index 0000000..45d00b9 --- /dev/null +++ b/boxes/ubuntu-cloud/metadata.json @@ -0,0 +1,7 @@ +{ + "provider": "lxc", + "vagrant-lxc-version": "0.0.1", + "template-name": "lxc-ubuntu-cloud", + "after-create-script": "set-vagrant-user.sh", + "tar-cache": "ubuntu-12.10-server-cloudimg-amd64-root.tar.gz" +} diff --git a/dummy-box-files/metadata.json b/dummy-box-files/metadata.json deleted file mode 100644 index 959aa0e..0000000 --- a/dummy-box-files/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"provider":"lxc"} diff --git a/setup-vagrant-dev-box b/setup-vagrant-dev-box index 76db20b..5917bef 100755 --- a/setup-vagrant-dev-box +++ b/setup-vagrant-dev-box @@ -5,12 +5,11 @@ raise 'You should not run this script from the dev box' if ENV['USER'] == 'vagra require 'bundler' require 'json' -IMAGE_ROOT = 'https://cloud-images.ubuntu.com/releases/quantal/release-20130206' -IMAGE_NAME = 'ubuntu-12.10-server-cloudimg-amd64-root.tar.gz' -VAGRANT_REPO = 'https://raw.github.com/mitchellh/vagrant/master' +IMAGE_ROOT = 'https://cloud-images.ubuntu.com/releases/quantal/release-20130206' +IMAGE_NAME = 'ubuntu-12.10-server-cloudimg-amd64-root.tar.gz' def download(source, destination) - destination = "#{File.dirname __FILE__}/cache/#{destination}" + destination = "#{File.dirname __FILE__}/#{destination}" return if File.exists?(destination) sh "wget #{source} -O #{destination}" @@ -61,8 +60,8 @@ end # Fetches vagrant submodule `git submodule update --init` -# Cache container image between vagrant box destructions -download "#{IMAGE_ROOT}/#{IMAGE_NAME}", IMAGE_NAME +# Download container image for building the base ubuntu-cloud box +download "#{IMAGE_ROOT}/#{IMAGE_NAME}", "boxes/ubuntu-cloud/#{IMAGE_NAME}" # Start vagrant sh 'vagrant up' diff --git a/tasks/boxes.rake b/tasks/boxes.rake new file mode 100644 index 0000000..a2117ee --- /dev/null +++ b/tasks/boxes.rake @@ -0,0 +1,10 @@ +namespace :boxes do + namespace :build do + desc 'Packages an Ubuntu cloud image as a Vagrant LXC box' + task 'ubuntu-cloud' do + sh 'mkdir -p boxes/output' + sh 'cp cache/ubuntu-12.10-server-cloudimg-amd64-root.tar.gz boxes/ubuntu-cloud' + sh 'cd boxes/ubuntu-cloud && tar -czf ../output/ubuntu-cloud.box ./*' + end + end +end diff --git a/tasks/package_dummy_box.rake b/tasks/package_dummy_box.rake deleted file mode 100644 index 02bcec4..0000000 --- a/tasks/package_dummy_box.rake +++ /dev/null @@ -1,4 +0,0 @@ -desc 'Packages a dummy Vagrant box to be used during development' -task :package_dummy_box do - sh 'cd dummy-box-files/ && tar -czf ../dummy-ubuntu-cloudimg.box ./*' -end From 6afd4c5d9a8c5a90b029b8b5a15b4a4f28e663ff Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 13:55:05 -0300 Subject: [PATCH 17/25] Add support for grabbing the container ip from lxc dns server --- lib/vagrant-lxc/container.rb | 28 ++++++++++++++++++++++++++++ spec/unit/container_spec.rb | 16 ++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index d24f765..7279e45 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -73,6 +73,34 @@ module Vagrant end end + def dhcp_ip + ip = '' + # Right after creation lxc reports the container as running + # before DNS is returning the right IP, so have to wait for a while + retryable(:on => LXC::Errors::ExecuteError, :tries => 10, :sleep => 1) do + # By default LXC supplies a dns server on 10.0.3.1 so we request the IP + # of our target from there. + # Tks to: https://github.com/neerolyte/vagueant/blob/master/bin/vagueant#L340 + r = (raw 'dig', @name, '@10.0.3.1', '+short') + + # If the command was a failure then raise an exception that is nicely + # handled by Vagrant. + if r.exit_code != 0 + if @interrupted + @logger.info("Exit code != 0, but interrupted. Ignoring.") + else + raise LXC::Errors::ExecuteError, :command => command.inspect + end + end + + ip = r.stdout.gsub("\r\n", "\n").strip + if ip.empty? + raise LXC::Errors::ExecuteError, 'Unable to identify container ip' + end + end + ip + end + # TODO: Review code below this line, it was pretty much a copy and paste from VirtualBox base driver def execute(*command, &block) # Get the options hash if it exists diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 9dccdfd..887a23b 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -175,4 +175,20 @@ describe Vagrant::LXC::Container do subject.state.should == :stopped end end + + describe 'dhcp ip' do + let(:name) { 'random-container-name' } + let(:ip) { "10.0.3.123" } + + before do + subject.stub(:raw) { + mock(stdout: "#{ip}\n", exit_code: 0) + } + end + + it 'digs the container ip from lxc dns server' do + subject.dhcp_ip.should == ip + subject.should have_received(:raw).with('dig', name, '@10.0.3.1', '+short') + end + end end From 10655d4c30a630dbcf0bf92017dc7add873735b8 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:38:53 -0300 Subject: [PATCH 18/25] Add support for running scripts after container creation --- lib/vagrant-lxc/container.rb | 21 ++++++++++++++++++--- spec/unit/container_spec.rb | 23 ++++++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/vagrant-lxc/container.rb b/lib/vagrant-lxc/container.rb index 7279e45..d13235e 100644 --- a/lib/vagrant-lxc/container.rb +++ b/lib/vagrant-lxc/container.rb @@ -16,6 +16,8 @@ module Vagrant # an UUID. class NotFound < StandardError; end + CONTAINERS_PATH = '/var/lib/lxc' + attr_reader :name def initialize(name) @@ -30,13 +32,26 @@ module Vagrant def create(metadata = {}) # FIXME: Ruby 1.8 users dont have SecureRandom # @logger.info('Creating container...') - name = SecureRandom.hex(6) + @name = SecureRandom.hex(6) public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s # TODO: Handle errors - lxc :create, '--template', metadata['template-name'], '--name', name, '--', '-S', public_key, '-T', metadata['tar-cache'] + lxc :create, '--template', metadata['template-name'], '--name', @name, '--', '-S', public_key, '-T', metadata['tar-cache'] - @name = name + @name + end + + def run_after_create_script(script) + private_key = Vagrant.source_root.join('keys', 'vagrant').expand_path.to_s + + @logger.debug 'Running after-create-script from box metadata' + cmd = [ + script, + '-r', "#{CONTAINERS_PATH}/#{@name}/rootfs", + '-k', private_key, + '-i', dhcp_ip + ] + execute *cmd end def start diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index 887a23b..bccb1ee 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -82,7 +82,7 @@ describe Vagrant::LXC::Container do let(:public_key_path) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s } before do - subject.stub(:lxc) + subject.stub(lxc: true) SecureRandom.stub(hex: name) subject.create 'template-name' => template_name, 'tar-cache' => tar_cache_path end @@ -99,6 +99,27 @@ describe Vagrant::LXC::Container do end end + describe 'after create script execution' do + let(:name) { 'random-container-name' } + let(:after_create_path) { '/path/to/after/create' } + let(:execute_cmd) { @execute_cmd } + let(:priv_key_path) { Vagrant.source_root.join('keys', 'vagrant').expand_path.to_s } + let(:ip) { '10.0.3.234' } + + before do + subject.stub(dhcp_ip: ip) + subject.stub(:execute) { |*args| @execute_cmd = args.join(' ') } + subject.run_after_create_script after_create_path + end + + it 'runs after-create-script when present passing required variables' do + execute_cmd.should include after_create_path + execute_cmd.should include "-r /var/lib/lxc/#{name}/rootfs" + execute_cmd.should include "-k #{priv_key_path}" + execute_cmd.should include "-i #{ip}" + end + end + describe 'destruction' do let(:name) { 'container-name' } From a9acde7da17a5dc2017057344ec566f8bbf76ec5 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:39:49 -0300 Subject: [PATCH 19/25] Prepends box directory to after-create-script defined on box metadata --- lib/vagrant-lxc/action/handle_box_metadata.rb | 9 +++++++-- spec/unit/action/handle_box_metadata_spec.rb | 7 ++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/vagrant-lxc/action/handle_box_metadata.rb b/lib/vagrant-lxc/action/handle_box_metadata.rb index 5a1ca81..f6784c8 100644 --- a/lib/vagrant-lxc/action/handle_box_metadata.rb +++ b/lib/vagrant-lxc/action/handle_box_metadata.rb @@ -17,9 +17,14 @@ module Vagrant metadata = box.metadata template_name = metadata['template-name'] + after_create = metadata['after-create-script'] ? + box.directory.join(metadata['after-create-script']).to_s : + nil + metadata.merge!( - 'template-name' => "vagrant-#{box.name}-#{template_name}", - 'tar-cache' => box.directory.join(metadata['tar-cache']).to_s + 'template-name' => "vagrant-#{box.name}-#{template_name}", + 'tar-cache' => box.directory.join(metadata['tar-cache']).to_s, + 'after-create-script' => after_create ) # Prepends "lxc-" to the template file so that `lxc-create` is able to find it diff --git a/spec/unit/action/handle_box_metadata_spec.rb b/spec/unit/action/handle_box_metadata_spec.rb index 384479e..afb2ec7 100644 --- a/spec/unit/action/handle_box_metadata_spec.rb +++ b/spec/unit/action/handle_box_metadata_spec.rb @@ -5,7 +5,8 @@ require 'vagrant-lxc/action/handle_box_metadata' describe Vagrant::LXC::Action::HandleBoxMetadata do let(:tar_cache) { 'template.zip' } let(:template_name) { 'ubuntu-lts' } - let(:metadata) { {'template-name' => template_name, 'tar-cache' => tar_cache} } + let(:after_create) { 'setup-vagrant-user.sh' } + let(:metadata) { {'template-name' => template_name, 'tar-cache' => tar_cache, 'after-create-script' => after_create} } let(:box) { mock(:box, name: 'box-name', metadata: metadata, directory: Pathname.new('/path/to/box')) } let(:machine) { mock(:machine, box: box) } let(:app) { mock(:app, call: true) } @@ -22,6 +23,10 @@ describe Vagrant::LXC::Action::HandleBoxMetadata do metadata['tar-cache'].should == "#{box.directory.to_s}/#{tar_cache}" end + it 'prepends box directory to after-create-script' do + metadata['after-create-script'].should == "#{box.directory.to_s}/#{after_create}" + end + it 'prepends vagrant and box name to template-name' do metadata['template-name'].should == "vagrant-#{box.name}-#{template_name}" end From 800fa49c836a1b65778a4f17c575c458a19bab21 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:40:03 -0300 Subject: [PATCH 20/25] Copy & paste is EVIL --- spec/unit/container_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/container_spec.rb b/spec/unit/container_spec.rb index bccb1ee..3ea90da 100644 --- a/spec/unit/container_spec.rb +++ b/spec/unit/container_spec.rb @@ -128,7 +128,7 @@ describe Vagrant::LXC::Container do subject.destroy end - it 'calls lxc-create with the right arguments' do + it 'calls lxc-destroy with the right arguments' do subject.should have_received(:lxc).with( :destroy, '--name', name, From 22e5a48af5584c4754b0bae29f2b6ed3df93bfa1 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:45:14 -0300 Subject: [PATCH 21/25] Add ssh_info to provider --- lib/vagrant-lxc/provider.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/vagrant-lxc/provider.rb b/lib/vagrant-lxc/provider.rb index 783d6d0..43f6a63 100644 --- a/lib/vagrant-lxc/provider.rb +++ b/lib/vagrant-lxc/provider.rb @@ -45,6 +45,18 @@ module Vagrant nil end + # Returns the SSH info for accessing the Container. + def ssh_info + # If the Container is not created then we cannot possibly SSH into it, so + # we return nil. + return nil if state == :not_created + + { + :host => @container.dhcp_ip, + :port => 22 # @driver.ssh_port(@machine.config.ssh.guest_port) + } + end + def state state_id = nil state_id = :not_created if !@container.name From a8cf5deee205d51d91959a305af1b217090047c8 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:47:44 -0300 Subject: [PATCH 22/25] Add middleware to run after create script on vagrant up --- lib/vagrant-lxc/action.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 45483b7..513acf9 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -78,6 +78,7 @@ module Vagrant end end b.use action_start + b.use AfterCreate end end @@ -155,8 +156,18 @@ module Vagrant class Create < BaseAction def call(env) - machine_id = env[:machine].provider.container.create(env[:machine].box.metadata) - env[:machine].id = machine_id + machine_id = env[:machine].provider.container.create(env[:machine].box.metadata) + env[:machine].id = machine_id + env[:just_created] = true + @app.call env + end + end + + class AfterCreate < BaseAction + def call(env) + if env[:just_created] && (script = env[:machine].box.metadata['after-create-script']) + env[:machine].provider.container.run_after_create_script script + end @app.call env end end From f87dca95370086bfc8318187cec2afde2702138f Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:48:15 -0300 Subject: [PATCH 23/25] vagrant ssh: check --- lib/vagrant-lxc/action.rb | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/vagrant-lxc/action.rb b/lib/vagrant-lxc/action.rb index 513acf9..bb7105c 100644 --- a/lib/vagrant-lxc/action.rb +++ b/lib/vagrant-lxc/action.rb @@ -132,6 +132,52 @@ module Vagrant end end + # This is the action that will exec into an SSH shell. + def self.action_ssh + Vagrant::Action::Builder.new.tap do |b| + b.use CheckLXC + b.use CheckCreated + # b.use CheckAccessible + b.use CheckRunning + b.use Vagrant::Action::Builtin::SSHExec + end + end + + # This is the action that will run a single SSH command. + def self.action_ssh_run + Vagrant::Action::Builder.new.tap do |b| + b.use CheckLXC + b.use CheckCreated + # b.use CheckAccessible + b.use CheckRunning + b.use Vagrant::Action::Builtin::SSHRun + end + end + + class CheckCreated < BaseAction + def call(env) + unless env[:machine].state.created? + raise Vagrant::Errors::VMNotCreatedError + end + + # Call the next if we have one (but we shouldn't, since this + # middleware is built to run with the Call-type middlewares) + @app.call(env) + end + end + + class CheckRunning < BaseAction + def call(env) + unless env[:machine].state.running? + raise Vagrant::Errors::VMNotCreatedError + end + + # Call the next if we have one (but we shouldn't, since this + # middleware is built to run with the Call-type middlewares) + @app.call(env) + end + end + class Created < BaseAction def call(env) # Set the result to be true if the machine is created. From fd611d3d1a2ab2f46791176e9e59bbde6a542f45 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:49:55 -0300 Subject: [PATCH 24/25] A whole bunch of tweaks to ubuntu-cloud box --- boxes/ubuntu-cloud/metadata.json | 4 +- boxes/ubuntu-cloud/setup-vagrant-user.sh | 58 +++++++++++++++++++ .../{lxc-ubuntu-cloud => ubuntu-cloud} | 0 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100755 boxes/ubuntu-cloud/setup-vagrant-user.sh rename boxes/ubuntu-cloud/{lxc-ubuntu-cloud => ubuntu-cloud} (100%) diff --git a/boxes/ubuntu-cloud/metadata.json b/boxes/ubuntu-cloud/metadata.json index 45d00b9..230f1f6 100644 --- a/boxes/ubuntu-cloud/metadata.json +++ b/boxes/ubuntu-cloud/metadata.json @@ -1,7 +1,7 @@ { "provider": "lxc", "vagrant-lxc-version": "0.0.1", - "template-name": "lxc-ubuntu-cloud", - "after-create-script": "set-vagrant-user.sh", + "template-name": "ubuntu-cloud", + "after-create-script": "setup-vagrant-user.sh", "tar-cache": "ubuntu-12.10-server-cloudimg-amd64-root.tar.gz" } diff --git a/boxes/ubuntu-cloud/setup-vagrant-user.sh b/boxes/ubuntu-cloud/setup-vagrant-user.sh new file mode 100755 index 0000000..a8f9897 --- /dev/null +++ b/boxes/ubuntu-cloud/setup-vagrant-user.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# Argument = -r -i -k + +CONTAINER_ROOTFS= +CONTAINER_IP= +VAGRANT_PRIVATE_KEY_PATH= + +options=$(getopt -o r:i:k: -- "$@") +eval set -- "$options" + +declare r CONTAINER_ROOTFS \ + i CONTAINER_IP \ + k VAGRANT_PRIVATE_KEY_PATH + +while true +do + case "$1" in + -r) CONTAINER_ROOTFS=$2; shift 2;; + -i) CONTAINER_IP=$2; shift 2;; + -k) VAGRANT_PRIVATE_KEY_PATH=$2; shift 2;; + *) break ;; + esac +done + +if [[ -z $CONTAINER_ROOTFS ]] || [[ -z $CONTAINER_IP ]] || [[ -z $VAGRANT_PRIVATE_KEY_PATH ]] +then + echo 'You forgot an argument!' + exit 1 +fi + +remote_setup_script() { + cat << EOF +useradd -d /home/vagrant -m vagrant -r -s /bin/bash +usermod -a -G admin vagrant +cp /etc/sudoers /etc/sudoers.orig +sed -i -e '/Defaults\s\+env_reset/a Defaults\texempt_group=admin' /etc/sudoers +sed -i -e 's/%admin\s\+ALL=(ALL)\s\+ALL/%admin ALL=NOPASSWD:ALL/g' /etc/sudoers +service sudo restart +sudo su vagrant -c "mkdir -p /home/vagrant/.ssh" +sudo su vagrant -c "curl -s -o /home/vagrant/.ssh/authorized_keys https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub" +EOF +} + +REMOTE_SETUP_SCRIPT_PATH="/tmp/setup-vagrant-user" + +# Ensures the private key has the right permissions +# Might not be needed after: https://github.com/mitchellh/vagrant/commit/d304cca35d19c5bd370330c74f003b6ac46e7f4a +chmod 0600 $VAGRANT_PRIVATE_KEY_PATH + +remote_setup_script > "${CONTAINER_ROOTFS}${REMOTE_SETUP_SCRIPT_PATH}" +chmod +x "${CONTAINER_ROOTFS}${REMOTE_SETUP_SCRIPT_PATH}" + +ssh ubuntu@"$CONTAINER_IP" \ + -o 'StrictHostKeyChecking no' \ + -o 'UserKnownHostsFile /dev/null' \ + -i $VAGRANT_PRIVATE_KEY_PATH \ + -- \ + sudo $REMOTE_SETUP_SCRIPT_PATH diff --git a/boxes/ubuntu-cloud/lxc-ubuntu-cloud b/boxes/ubuntu-cloud/ubuntu-cloud similarity index 100% rename from boxes/ubuntu-cloud/lxc-ubuntu-cloud rename to boxes/ubuntu-cloud/ubuntu-cloud From 3f5f1cc75302a7dc0f6c99f4bd1748de7ad963a1 Mon Sep 17 00:00:00 2001 From: Fabio Rehm Date: Sat, 2 Mar 2013 16:51:28 -0300 Subject: [PATCH 25/25] Clean up dev vagrantfile --- dev/Vagrantfile | 112 +++--------------------------------------------- 1 file changed, 6 insertions(+), 106 deletions(-) diff --git a/dev/Vagrantfile b/dev/Vagrantfile index b2366cb..013b83f 100644 --- a/dev/Vagrantfile +++ b/dev/Vagrantfile @@ -1,111 +1,11 @@ # -*- mode: ruby -*- # vi: set ft=ruby : +require '../lib/vagrant-lxc' + Vagrant.configure("2") do |config| - # All Vagrant configuration is done here. The most common configuration - # options are documented and commented below. For a complete reference, - # please see the online documentation at vagrantup.com. - - # Every Vagrant virtual environment requires a box to build off of. - config.vm.box = "base" - - # The url from where the 'config.vm.box' box will be fetched if it - # doesn't already exist on the user's system. - # config.vm.box_url = "http://domain.com/path/to/above.box" - - # Create a forwarded port mapping which allows access to a specific port - # within the machine from a port on the host machine. In the example below, - # accessing "localhost:8080" will access port 80 on the guest machine. - # config.vm.network :forwarded_port, 80, 8080 - - # Create a private network, which allows host-only access to the machine - # using a specific IP. - # config.vm.network :private_network, "192.168.33.10" - - # Create a public network, which generally matched to bridged network. - # Bridged networks make the machine appear as another physical device on - # your network. - # config.vm.network :public_network - - # Share an additional folder to the guest VM. The first argument is - # the path on the host to the actual folder. The second argument is - # the path on the guest to mount the folder. And the optional third - # argument is a set of non-required options. - # config.vm.synced_folder "../data", "/vagrant_data" - - # Provider-specific configuration so you can fine-tune various - # backing providers for Vagrant. These expose provider-specific options. - # Example for VirtualBox: - # - # config.vm.provider :virtualbox do |vb| - # # Don't boot with headless mode - # vb.gui = true - # - # # Use VBoxManage to customize the VM. For example to change memory: - # vb.customize ["modifyvm", :id, "--memory", "1024"] - # end - # - # View the documentation for the provider you're using for more - # information on available options. - - # Enable provisioning with Puppet stand alone. Puppet manifests - # are contained in a directory path relative to this Vagrantfile. - # You will need to create the manifests directory and a manifest in - # the file base.pp in the manifests_path directory. - # - # An example Puppet manifest to provision the message of the day: - # - # # group { "puppet": - # # ensure => "present", - # # } - # # - # # File { owner => 0, group => 0, mode => 0644 } - # # - # # file { '/etc/motd': - # # content => "Welcome to your Vagrant-built virtual machine! - # # Managed by Puppet.\n" - # # } - # - # config.vm.provision :puppet do |puppet| - # puppet.manifests_path = "manifests" - # puppet.manifest_file = "base.pp" - # end - - # Enable provisioning with chef solo, specifying a cookbooks path, roles - # path, and data_bags path (all relative to this Vagrantfile), and adding - # some recipes and/or roles. - # - # config.vm.provision :chef_solo do |chef| - # chef.cookbooks_path = "../my-recipes/cookbooks" - # chef.roles_path = "../my-recipes/roles" - # chef.data_bags_path = "../my-recipes/data_bags" - # chef.add_recipe "mysql" - # chef.add_role "web" - # - # # You may also specify custom JSON attributes: - # chef.json = { :mysql_password => "foo" } - # end - - # Enable provisioning with chef server, specifying the chef server URL, - # and the path to the validation key (relative to this Vagrantfile). - # - # The Opscode Platform uses HTTPS. Substitute your organization for - # ORGNAME in the URL and validation key. - # - # If you have your own Chef Server, use the appropriate URL, which may be - # HTTP instead of HTTPS depending on your configuration. Also change the - # validation key to validation.pem. - # - # config.vm.provision :chef_client do |chef| - # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" - # chef.validation_key_path = "ORGNAME-validator.pem" - # end - # - # If you're using the Opscode platform, your validator client is - # ORGNAME-validator, replacing ORGNAME with your organization name. - # - # If you have your own Chef Server, the default validation client name is - # chef-validator, unless you changed the configuration. - # - # chef.validation_client_name = "ORGNAME-validator" + config.vm.box = "ubuntu-cloud" + config.vm.provider :lxc do |lxc| + # ... soon to come lxc configs... + end end