diff --git a/lib/vagrant-lxc/container/cli.rb b/lib/vagrant-lxc/container/cli.rb index d4d75ed..01bce47 100644 --- a/lib/vagrant-lxc/container/cli.rb +++ b/lib/vagrant-lxc/container/cli.rb @@ -1,5 +1,3 @@ -require 'unit_helper' - require "vagrant/util/retryable" require "vagrant/util/subprocess" @@ -9,6 +7,10 @@ module Vagrant module LXC class Container class CLI + attr_accessor :name + + class TransitionBlockNotProvided < RuntimeError; end + # Include this so we can use `Subprocess` more easily. include Vagrant::Util::Retryable @@ -18,17 +20,57 @@ module Vagrant end def list - containers = lxc :ls - containers.split(/\s+/).uniq + run(:ls).split(/\s+/).uniq + end + + def state + if @name && run(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/ + $1.downcase.to_sym + elsif @name + :unknown + end + end + + def create(template, template_opts = {}) + extra = template_opts.to_a.flatten + extra.unshift '--' unless extra.empty? + + run :create, + # lxc-create options + '--template', template, + '--name', @name, + *extra + end + + def destroy + run :destroy, '--name', @name + end + + def start(configs = {}) + configs = configs.map { |key, value| ["-s", "#{key}=#{value}"] }.flatten + run :start, '-d', '--name', @name, *configs + end + + def shutdown + run :shutdown, '--name', @name + end + + def transition_to(state, &block) + raise TransitionBlockNotProvided unless block_given? + + yield self + + run :wait, '--name', @name, '--state', state.to_s.upcase end private - def lxc(command, *args) + def run(command, *args) execute('sudo', "lxc-#{command}", *args) end - # TODO: Review code below this line, it was pretty much a copy and paste from VirtualBox base driver + # TODO: Review code below this line, it was pretty much a copy and + # paste from VirtualBox base driver and has no tests def execute(*command, &block) # Get the options hash if it exists opts = {} diff --git a/spec/unit/container/cli_spec.rb b/spec/unit/container/cli_spec.rb index 1e9068d..11152eb 100644 --- a/spec/unit/container/cli_spec.rb +++ b/spec/unit/container/cli_spec.rb @@ -7,24 +7,142 @@ describe Vagrant::LXC::Container::CLI do describe 'list' do let(:lxc_ls_out) { "dup-container\na-container dup-container" } let(:exec_args) { @exec_args } - let(:result) { subject.list } + let(:result) { @result } before do - Vagrant::Util::Subprocess.stub(:execute) { |*args| - @exec_args = args - stub(exit_code: 0, stdout: lxc_ls_out) - } + subject.stub(:run).with(:ls).and_return(lxc_ls_out) + @result = subject.list end - it 'grabs previously created containers from lxc-ls' do - result.should be_an Enumerable - result.should include 'a-container' - result.should include 'dup-container' - exec_args.should include 'lxc-ls' + it 'grabs previously created containers from lxc-ls output' do + result.should be_an Enumerable + result.should include 'a-container' + result.should include 'dup-container' end it 'removes duplicates from lxc-ls output' do result.uniq.should == result end end + + describe 'create' do + let(:template) { 'quantal-64' } + let(:name) { 'quantal-container' } + let(:template_args) { { '--extra-param' => 'param', '--other' => 'value' } } + + subject { described_class.new(name) } + + before do + subject.stub(:run) + subject.create(template, template_args) + end + + it 'issues a lxc-create with provided template, container name and hash of arguments' do + subject.should have_received(:run).with( + :create, + '--template', template, + '--name', name, + '--', + '--extra-param', 'param', + '--other', 'value' + ) + end + end + + describe 'destroy' do + let(:name) { 'a-container-for-destruction' } + + subject { described_class.new(name) } + + before do + subject.stub(:run) + subject.destroy + end + + it 'issues a lxc-destroy with container name' do + subject.should have_received(:run).with(:destroy, '--name', name) + end + end + + describe 'start' do + let(:name) { 'a-container' } + subject { described_class.new(name) } + + before do + subject.stub(:run) + end + + it 'starts container on the background' do + subject.start + subject.should have_received(:run).with( + :start, + '-d', + '--name', name + ) + end + + it 'uses provided hash to configure the container' do + subject.start('lxc.config' => 'value', 'lxc.other' => 'value') + subject.should have_received(:run).with(:start, '-d', '--name', name, + '-s', 'lxc.config=value', + '-s', 'lxc.other=value' + ) + end + end + + describe 'shutdown' do + let(:name) { 'a-running-container' } + subject { described_class.new(name) } + + before do + subject.stub(:run) + subject.shutdown + end + + it 'issues a lxc-shutdown with provided container name' do + subject.should have_received(:run).with(:shutdown, '--name', name) + end + end + + describe 'state' do + let(:name) { 'a-container' } + subject { described_class.new(name) } + + before do + subject.stub(:run).and_return("state: STOPPED\npid: 2") + end + + it 'calls lxc-info with the right arguments' do + subject.state + subject.should have_received(:run).with(:info, '--name', name) + end + + it 'maps the output of lxc-info status out to a symbol' do + subject.state.should == :stopped + end + end + + describe 'transition block' do + let(:name) { 'a-running-container' } + subject { described_class.new(name) } + + before { subject.stub(:run) } + + it 'yields cli object' do + subject.stub(:shutdown) + subject.transition_to(:stopped) { |c| c.shutdown } + subject.should have_received(:shutdown) + end + + it 'throws an exception if block is not provided' do + expect { + subject.transition_to(:running) + }.to raise_error(described_class::TransitionBlockNotProvided) + end + + it 'waits for the expected container state using lxc-wait' do + subject.transition_to(:running) { } + subject.should have_received(:run).with(:wait, '--name', name, '--state', 'RUNNING') + end + end end