vagrant-lxc 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.vimrc +1 -1
  4. data/CHANGELOG.md +22 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +10 -9
  7. data/README.md +43 -29
  8. data/boxes/quantal64/download-ubuntu +21 -20
  9. data/boxes/quantal64/lxc-template +11 -11
  10. data/boxes/quantal64/metadata.json +1 -1
  11. data/development/Vagrantfile +8 -4
  12. data/example/Vagrantfile +3 -15
  13. data/lib/vagrant-lxc.rb +0 -2
  14. data/lib/vagrant-lxc/action.rb +1 -14
  15. data/lib/vagrant-lxc/action/boot.rb +8 -9
  16. data/lib/vagrant-lxc/action/check_created.rb +6 -2
  17. data/lib/vagrant-lxc/action/check_running.rb +6 -2
  18. data/lib/vagrant-lxc/action/compress_rootfs.rb +1 -1
  19. data/lib/vagrant-lxc/action/create.rb +15 -7
  20. data/lib/vagrant-lxc/action/created.rb +6 -2
  21. data/lib/vagrant-lxc/action/destroy.rb +6 -2
  22. data/lib/vagrant-lxc/action/disconnect.rb +5 -1
  23. data/lib/vagrant-lxc/action/forced_halt.rb +3 -3
  24. data/lib/vagrant-lxc/action/forward_ports.rb +2 -2
  25. data/lib/vagrant-lxc/action/handle_box_metadata.rb +38 -27
  26. data/lib/vagrant-lxc/action/is_running.rb +6 -2
  27. data/lib/vagrant-lxc/action/share_folders.rb +8 -8
  28. data/lib/vagrant-lxc/config.rb +20 -10
  29. data/lib/vagrant-lxc/driver.rb +162 -0
  30. data/lib/vagrant-lxc/driver/builder.rb +21 -0
  31. data/lib/vagrant-lxc/{container → driver}/cli.rb +16 -11
  32. data/lib/vagrant-lxc/driver/fetch_ip_from_dnsmasq.rb +41 -0
  33. data/lib/vagrant-lxc/driver/fetch_ip_with_attach.rb +29 -0
  34. data/lib/vagrant-lxc/errors.rb +10 -0
  35. data/lib/vagrant-lxc/plugin.rb +4 -0
  36. data/lib/vagrant-lxc/provider.rb +14 -11
  37. data/lib/vagrant-lxc/version.rb +1 -1
  38. data/spec/fixtures/sample-ip-addr-output +2 -0
  39. data/spec/spec_helper.rb +1 -0
  40. data/spec/unit/action/compress_rootfs_spec.rb +4 -4
  41. data/spec/unit/action/forward_ports_spec.rb +3 -3
  42. data/spec/unit/action/handle_box_metadata_spec.rb +52 -26
  43. data/spec/unit/{container → driver}/cli_spec.rb +17 -19
  44. data/spec/unit/driver_spec.rb +173 -0
  45. data/tasks/boxes.rake +3 -3
  46. metadata +13 -15
  47. data/lib/vagrant-lxc/action/base_action.rb +0 -11
  48. data/lib/vagrant-lxc/action/network.rb +0 -21
  49. data/lib/vagrant-lxc/container.rb +0 -141
  50. data/lib/vagrant-lxc/machine_state.rb +0 -25
  51. data/spec/fixtures/sample-ifconfig-output +0 -18
  52. data/spec/unit/container_spec.rb +0 -147
  53. data/spec/unit/machine_state_spec.rb +0 -39
@@ -0,0 +1,162 @@
1
+ require "vagrant/util/retryable"
2
+ require "vagrant/util/subprocess"
3
+
4
+ require "vagrant-lxc/errors"
5
+ require "vagrant-lxc/driver/cli"
6
+
7
+ module Vagrant
8
+ module LXC
9
+ class Driver
10
+ # This is raised if the container can't be found when initializing it with
11
+ # a name.
12
+ class ContainerNotFound < StandardError; end
13
+
14
+ attr_reader :container_name,
15
+ :customizations
16
+
17
+ def initialize(container_name, cli = CLI.new(container_name))
18
+ @container_name = container_name
19
+ @cli = cli
20
+ @logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
21
+ @customizations = []
22
+ end
23
+
24
+ def validate!
25
+ raise ContainerNotFound if @container_name && ! @cli.list.include?(@container_name)
26
+ end
27
+
28
+ def base_path
29
+ Pathname.new("#{CONTAINERS_PATH}/#{@container_name}")
30
+ end
31
+
32
+ def rootfs_path
33
+ Pathname.new(base_path.join('config').read.match(/^lxc\.rootfs\s+=\s+(.+)$/)[1])
34
+ end
35
+
36
+ def create(name, template_path, template_options = {})
37
+ @cli.name = @container_name = name
38
+
39
+ import_template(template_path) do |template_name|
40
+ @logger.debug "Creating container..."
41
+ @cli.create template_name, template_options
42
+ end
43
+ end
44
+
45
+ def share_folders(folders)
46
+ folders.each do |folder|
47
+ guestpath = rootfs_path.join(folder[:guestpath].gsub(/^\//, ''))
48
+ unless guestpath.directory?
49
+ begin
50
+ @logger.debug("Guest path doesn't exist, creating: #{guestpath}")
51
+ system "sudo mkdir -p #{guestpath.to_s}"
52
+ rescue Errno::EACCES
53
+ raise Vagrant::Errors::SharedFolderCreateFailed, :path => guestpath.to_s
54
+ end
55
+ end
56
+
57
+ @customizations << ['mount.entry', "#{folder[:hostpath]} #{guestpath} none bind 0 0"]
58
+ end
59
+ end
60
+
61
+ def start(customizations)
62
+ @logger.info('Starting container...')
63
+
64
+ if ENV['LXC_START_LOG_FILE']
65
+ extra = ['-o', ENV['LXC_START_LOG_FILE'], '-l', 'DEBUG']
66
+ end
67
+ customizations = customizations + @customizations
68
+
69
+ @cli.transition_to(:running) { |c| c.start(customizations, (extra || nil)) }
70
+ end
71
+
72
+ def halt
73
+ @logger.info('Shutting down container...')
74
+
75
+ # TODO: issue an lxc-stop if a timeout gets reached
76
+ @cli.transition_to(:stopped) { |c| c.shutdown }
77
+ end
78
+
79
+ def destroy
80
+ @cli.destroy
81
+ end
82
+
83
+ # TODO: This needs to be reviewed and specs needs to be written
84
+ def compress_rootfs
85
+ rootfs_dirname = File.dirname rootfs_path
86
+ basename = rootfs_path.to_s.gsub(/^#{Regexp.escape rootfs_dirname}\//, '')
87
+ # TODO: Pass in tmpdir so we can clean up from outside
88
+ target_path = "#{Dir.mktmpdir}/rootfs.tar.gz"
89
+
90
+ Dir.chdir base_path do
91
+ @logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
92
+ system "sudo rm -f rootfs.tar.gz && sudo tar --numeric-owner -czf #{target_path} #{basename}/*"
93
+
94
+ @logger.info "Changing rootfs tarbal owner"
95
+ system "sudo chown #{ENV['USER']}:#{ENV['USER']} #{target_path}"
96
+ end
97
+
98
+ target_path
99
+ end
100
+
101
+ def state
102
+ if @container_name
103
+ @cli.state
104
+ end
105
+ end
106
+
107
+ def assigned_ip
108
+ ip = ''
109
+ retryable(:on => LXC::Errors::ExecuteError, :tries => 10, :sleep => 3) do
110
+ unless ip = get_container_ip_from_ip_addr
111
+ # retry
112
+ raise LXC::Errors::ExecuteError, :command => "lxc-attach"
113
+ end
114
+ end
115
+ ip
116
+ end
117
+
118
+ # From: https://github.com/lxc/lxc/blob/staging/src/python-lxc/lxc/__init__.py#L371-L385
119
+ def get_container_ip_from_ip_addr
120
+ output = @cli.attach '/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'eth0', namespaces: 'network'
121
+ if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
122
+ return $1.to_s
123
+ end
124
+ end
125
+
126
+ protected
127
+
128
+ # Root folder where container configs are stored
129
+ CONTAINERS_PATH = '/var/lib/lxc'
130
+
131
+ def base_path
132
+ Pathname.new("#{CONTAINERS_PATH}/#{@container_name}")
133
+ end
134
+
135
+ def import_template(path)
136
+ template_name = "vagrant-tmp-#{@container_name}"
137
+ tmp_template_path = templates_path.join("lxc-#{template_name}").to_s
138
+
139
+ @logger.debug 'Copying LXC template into place'
140
+ system(%Q[sudo su root -c "cp #{path} #{tmp_template_path}"])
141
+
142
+ yield template_name
143
+ ensure
144
+ system(%Q[sudo su root -c "rm #{tmp_template_path}"])
145
+ end
146
+
147
+ TEMPLATES_PATH_LOOKUP = %w(
148
+ /usr/share/lxc/templates
149
+ /usr/lib/lxc/templates
150
+ )
151
+ def templates_path
152
+ return @templates_path if @templates_path
153
+
154
+ path = TEMPLATES_PATH_LOOKUP.find { |candidate| File.directory?(candidate) }
155
+ # TODO: Raise an user friendly error
156
+ raise 'Unable to identify lxc templates path!' unless path
157
+
158
+ @templates_path = Pathname(path)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'fetch_ip_with_attach'
2
+ require_relative 'fetch_ip_from_dnsmasq'
3
+
4
+ module Vagrant
5
+ module LXC
6
+ class Driver
7
+ class Builder
8
+ def self.build(id)
9
+ version = CLI.new.version.match(/^(\d+\.\d+)\./)[1].to_f
10
+ Driver.new(id).tap do |driver|
11
+ mod = version >= 0.8 ?
12
+ Driver::FetchIpWithAttach :
13
+ Driver::FetchIpFromDsnmasq
14
+
15
+ driver.extend(mod)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -5,7 +5,7 @@ require "vagrant-lxc/errors"
5
5
 
6
6
  module Vagrant
7
7
  module LXC
8
- class Container
8
+ class Driver
9
9
  class CLI
10
10
  attr_accessor :name
11
11
 
@@ -23,6 +23,15 @@ module Vagrant
23
23
  run(:ls).split(/\s+/).uniq
24
24
  end
25
25
 
26
+ def version
27
+ if run(:version) =~ /lxc version:\s+(.+)\s*$/
28
+ $1.downcase
29
+ else
30
+ # TODO: Raise an user friendly error
31
+ raise 'Unable to parse lxc version!'
32
+ end
33
+ end
34
+
26
35
  def state
27
36
  if @name && run(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/
28
37
  $1.downcase.to_sym
@@ -31,28 +40,24 @@ module Vagrant
31
40
  end
32
41
  end
33
42
 
34
- def create(template, target_rootfs_path, template_opts = {})
43
+ def create(template, template_opts = {})
35
44
  extra = template_opts.to_a.flatten
36
45
  extra.unshift '--' unless extra.empty?
37
46
 
38
- rootfs_args = target_rootfs_path ?
39
- ['-B', 'dir', '--dir', target_rootfs_path] :
40
- []
41
-
42
47
  run :create,
43
48
  '--template', template,
44
49
  '--name', @name,
45
- *(rootfs_args + extra)
50
+ *extra
46
51
  end
47
52
 
48
53
  def destroy
49
54
  run :destroy, '--name', @name
50
55
  end
51
56
 
52
- def start(configs = [], extra_opts = [])
53
- configs = configs.map { |conf| ["-s", conf] }.flatten
54
- configs += extra_opts if extra_opts
55
- run :start, '-d', '--name', @name, *configs
57
+ def start(overrides = [], extra_opts = [])
58
+ options = overrides.map { |key, value| ["-s", "lxc.#{key}=#{value}"] }.flatten
59
+ options += extra_opts if extra_opts
60
+ run :start, '-d', '--name', @name, *options
56
61
  end
57
62
 
58
63
  def shutdown
@@ -0,0 +1,41 @@
1
+ module Vagrant
2
+ module LXC
3
+ class Driver
4
+ module FetchIpFromDsnmasq
5
+ def assigned_ip
6
+ @logger.debug 'Loading ip from dnsmasq leases'
7
+ ip = nil
8
+ # TODO: Use Vagrant::Util::Retryable
9
+ 10.times do
10
+ if dnsmasq_leases =~ /#{Regexp.escape mac_address}\s+([0-9.]+)\s+/
11
+ ip = $1.to_s
12
+ break
13
+ else
14
+ @logger.debug 'Ip could not be parsed from dnsmasq leases file'
15
+ sleep 2
16
+ end
17
+ end
18
+ # TODO: Raise an user friendly error
19
+ raise 'Unable to identify container IP!' unless ip
20
+ ip
21
+ end
22
+
23
+ def mac_address
24
+ @mac_address ||= base_path.join('config').read.match(/^lxc\.network\.hwaddr\s+=\s+(.+)$/)[1]
25
+ end
26
+
27
+ LEASES_PATHS = %w(
28
+ /var/lib/misc/dnsmasq.leases
29
+ /var/lib/dnsmasq/dnsmasq.leases
30
+ /var/db/dnsmasq.leases
31
+ )
32
+
33
+ def dnsmasq_leases
34
+ LEASES_PATHS.map do |path|
35
+ File.read(path) if File.exists?(path)
36
+ end.join("\n")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ module Vagrant
2
+ module LXC
3
+ class Driver
4
+ module FetchIpWithAttach
5
+ # Include this so we can use `Subprocess` more easily.
6
+ include Vagrant::Util::Retryable
7
+
8
+ def assigned_ip
9
+ ip = ''
10
+ retryable(:on => LXC::Errors::ExecuteError, :tries => 10, :sleep => 3) do
11
+ unless ip = get_container_ip_from_ip_addr
12
+ # retry
13
+ raise LXC::Errors::ExecuteError, :command => "lxc-attach"
14
+ end
15
+ end
16
+ ip
17
+ end
18
+
19
+ # From: https://github.com/lxc/lxc/blob/staging/src/python-lxc/lxc/__init__.py#L371-L385
20
+ def get_container_ip_from_ip_addr
21
+ output = @cli.attach '/sbin/ip', '-4', 'addr', 'show', 'scope', 'global', 'eth0', namespaces: 'network'
22
+ if output =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
23
+ return $1.to_s
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,12 +1,22 @@
1
+ require 'vagrant/errors'
2
+
1
3
  module Vagrant
2
4
  module LXC
3
5
  module Errors
4
6
  class ExecuteError < Vagrant::Errors::VagrantError
5
7
  error_key(:lxc_execute_error)
6
8
  end
9
+
10
+ # Box related errors
7
11
  class TemplateFileMissing < Vagrant::Errors::VagrantError
8
12
  error_key(:lxc_template_file_missing)
9
13
  end
14
+ class RootFSTarballMissing < Vagrant::Errors::VagrantError
15
+ error_key(:lxc_invalid_box_version)
16
+ end
17
+ class InvalidBoxVersion < Vagrant::Errors::VagrantError
18
+ error_key(:lxc_invalid_box_version)
19
+ end
10
20
  end
11
21
  end
12
22
  end
@@ -11,6 +11,10 @@ module Vagrant
11
11
 
12
12
  provider(:lxc) do
13
13
  require File.expand_path("../provider", __FILE__)
14
+
15
+ I18n.load_path << File.expand_path(File.dirname(__FILE__) + '/../../locales/en.yml')
16
+ I18n.reload!
17
+
14
18
  Provider
15
19
  end
16
20
 
@@ -1,13 +1,13 @@
1
1
  require "log4r"
2
2
 
3
3
  require "vagrant-lxc/action"
4
- require "vagrant-lxc/container"
5
- require "vagrant-lxc/machine_state"
4
+ require "vagrant-lxc/driver"
5
+ require "vagrant-lxc/driver/builder"
6
6
 
7
7
  module Vagrant
8
8
  module LXC
9
9
  class Provider < Vagrant.plugin("2", :provider)
10
- attr_reader :container
10
+ attr_reader :driver
11
11
 
12
12
  def initialize(machine)
13
13
  @logger = Log4r::Logger.new("vagrant::provider::lxc")
@@ -23,9 +23,9 @@ module Vagrant
23
23
 
24
24
  begin
25
25
  @logger.debug("Instantiating the container for: #{id.inspect}")
26
- @container = Container.new(id)
27
- @container.validate!
28
- rescue Container::NotFound
26
+ @driver = Driver::Builder.build(id)
27
+ @driver.validate!
28
+ rescue Driver::ContainerNotFound
29
29
  # The container doesn't exist, so we probably have a stale
30
30
  # ID. Just clear the id out of the machine and reload it.
31
31
  @logger.debug("Container not found! Clearing saved machine ID and reloading.")
@@ -40,7 +40,6 @@ module Vagrant
40
40
  # exists, otherwise return nil to show that we don't support the
41
41
  # given action.
42
42
  action_method = "action_#{name}"
43
- # TODO: Rename to singular
44
43
  return LXC::Action.send(action_method) if LXC::Action.respond_to?(action_method)
45
44
  nil
46
45
  end
@@ -52,17 +51,21 @@ module Vagrant
52
51
  return nil if state == :not_created
53
52
 
54
53
  {
55
- :host => @container.assigned_ip,
54
+ :host => @driver.assigned_ip,
56
55
  :port => @machine.config.ssh.guest_port
57
56
  }
58
57
  end
59
58
 
60
59
  def state
61
60
  state_id = nil
62
- state_id = :not_created if !@container.name
63
- state_id = @container.state if !state_id
61
+ state_id = :not_created if !@driver.container_name
62
+ state_id = @driver.state if !state_id
64
63
  state_id = :unknown if !state_id
65
- LXC::MachineState.new(state_id)
64
+
65
+ short = state_id.to_s.gsub("_", " ")
66
+ long = I18n.t("vagrant.commands.status.#{state_id}")
67
+
68
+ Vagrant::MachineState.new(state_id, short, long)
66
69
  end
67
70
 
68
71
  def to_s
@@ -1,5 +1,5 @@
1
1
  module Vagrant
2
2
  module LXC
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -0,0 +1,2 @@
1
+ 49: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
2
+ inet 10.0.254.137/24 brd 10.0.254.255 scope global eth0
data/spec/spec_helper.rb CHANGED
@@ -17,6 +17,7 @@ Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each { |f| require f }
17
17
  if ENV['VERIFY_CONSTANT_NAMES']
18
18
  require 'vagrant-lxc/plugin'
19
19
  require 'vagrant-lxc/provider'
20
+ require 'vagrant-lxc/config'
20
21
  end
21
22
 
22
23
  require 'rspec/fire'
@@ -6,8 +6,8 @@ describe Vagrant::LXC::Action::CompressRootFS do
6
6
  let(:app) { mock(:app, call: true) }
7
7
  let(:env) { {machine: machine, ui: stub(info: true)} }
8
8
  let(:machine) { fire_double('Vagrant::Machine', provider: provider) }
9
- let(:provider) { fire_double('Vagrant::LXC::Provider', container: container) }
10
- let(:container) { fire_double('Vagrant::LXC::Container', compress_rootfs: compressed_rootfs_path) }
9
+ let(:provider) { fire_double('Vagrant::LXC::Provider', driver: driver) }
10
+ let(:driver) { fire_double('Vagrant::LXC::Driver', compress_rootfs: compressed_rootfs_path) }
11
11
  let(:compressed_rootfs_path) { '/path/to/rootfs.tar.gz' }
12
12
 
13
13
  subject { described_class.new(app, env) }
@@ -17,8 +17,8 @@ describe Vagrant::LXC::Action::CompressRootFS do
17
17
  subject.call(env)
18
18
  end
19
19
 
20
- it 'asks the container to compress its rootfs' do
21
- container.should have_received(:compress_rootfs)
20
+ it "asks the driver to compress container's rootfs" do
21
+ driver.should have_received(:compress_rootfs)
22
22
  end
23
23
 
24
24
  it 'sets export.temp_dir on action env' do