vagrant-lxc 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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