lxc-ruby 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ *.tmproj
5
+ *~
6
+ .DS_Store
7
+ .\#*
8
+ .bundle
9
+ .config
10
+ .yardoc
11
+ Gemfile.lock
12
+ InstalledFiles
13
+ \#*
14
+ _yardoc
15
+ coverage
16
+ doc/
17
+ lib/bundler/man
18
+ pkg
19
+ rdoc
20
+ spec/reports
21
+ test/tmp
22
+ test/version_tmp
23
+ tmp
24
+ tmtags
25
+ lib/test.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format=nested
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - rbx-18mode
7
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # LXC Ruby Wrapper
2
+
3
+ Ruby wrapper to [LXC](http://lxc.sourceforge.net/) cli tools.
4
+
5
+ Provides a simple ruby dsl and json API to manage containers.
6
+
7
+ ## Build status
8
+
9
+ This library uses Travis-CI service for automated tests.
10
+
11
+ [![Build Status](https://secure.travis-ci.org/sosedoff/lxc-ruby.png?branch=master)](http://travis-ci.org/sosedoff/lxc-ruby)
12
+
13
+ ## Requirements
14
+
15
+ Supported LXC versions:
16
+
17
+ - 0.7.5
18
+ - 0.8.0-rc1
19
+ - 0.8.0-rc2 - in works
20
+
21
+ For testing purposes you can use [Vagrant](http://vagrantup.com/) or [VirtualBox](https://www.virtualbox.org/). Most of functionality
22
+ was tested on Ubuntu 11.04 / 11.10. Additional boxes could be found [here](http://www.vagrantbox.es/)
23
+
24
+ ## Installation
25
+
26
+ As for now this gem is not released yet, so use gem install task:
27
+
28
+ ```
29
+ rake build
30
+ rake install
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ You should have LXC already installed on your system before using the library.
36
+
37
+ Example:
38
+
39
+ ```ruby
40
+ require 'lxc'
41
+
42
+ # Check if all lxc binaries are installed
43
+ LXC.installed?
44
+
45
+ # Get LXC version
46
+ LXC.version
47
+
48
+ # Get current LXC configuration
49
+ LXC.config
50
+
51
+ # Get a list of all containers
52
+ LXC.containers
53
+
54
+ # Get a single container by name
55
+ LXC.container('name')
56
+ ```
57
+
58
+ Container instance is a simple abstaction for lxc's container tools:
59
+
60
+ ```ruby
61
+ c = LXC.container('foo')
62
+
63
+ # Get current status of container
64
+ c.status # => {:state => 'RUNNING', :pid => 1234}
65
+
66
+ # Check if container exists?
67
+ # this is needed since lxc does not raise any errors if container is
68
+ # not present in the system, and returns the same result as if container
69
+ # is actually stopped
70
+ c.exists? # => true
71
+
72
+ # Status helpers
73
+ c.running? # => true
74
+ c.frozen? # => false
75
+
76
+ # Start and stop containers
77
+ c.start # => {:state => 'RUNNING', :pid => 1234}
78
+ c.stop # => {:state => 'STOPPED', :pid => -1}
79
+
80
+ # Free and unfreeze (also returns current status)
81
+ c.freeze
82
+ c.unfreeze
83
+
84
+ # Get container memory usage (in bytes)
85
+ c.memory_usage
86
+ c.memory_limit
87
+
88
+ # Get running processes
89
+ c.processes
90
+ # =>
91
+ #[{"pid"=>"27404",
92
+ # "user"=>"root",
93
+ # "cpu"=>"0.0",
94
+ # "memory"=>"0.1",
95
+ # "command"=>"/sbin/init",
96
+ # "args"=>""}]
97
+
98
+ # Destroy container
99
+ c.destroy # => true
100
+ ```
101
+
102
+ To create a new container:
103
+
104
+ ``` ruby
105
+ c = LXC::Container.new('foo')
106
+ c.create(path_to_lxc_config)
107
+ ```
108
+
109
+ This method invokes ```lxc-create -n NAME -f CONFIG``` command. It *DOES NOT* create
110
+ any rootfs images or configures anything.
111
+
112
+ ### Running with sudo
113
+
114
+ By default LXC does not allow to run its command under unprivileged user. There are
115
+ two ways to make it work:
116
+
117
+ **Using sudo**
118
+
119
+ ```ruby
120
+ LXC.use_sudo = true
121
+ ```
122
+
123
+ **Using lxc-setcap**
124
+
125
+ If you want to make container usable by non-root users, run lxc-setcap as root, and some capabilities will be set so that normal users will be able to use the container utils. This is not done by default, though, and you have to explicitly allow it.
126
+
127
+ ## Testing
128
+
129
+ To run the test suite execute:
130
+
131
+ ```
132
+ rake test
133
+ ```
134
+
135
+ ## License
136
+
137
+ Copyright (c) 2012 Dan Sosedoff.
138
+
139
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
140
+
141
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
142
+
143
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:test) do |t|
6
+ t.pattern = 'spec/*_spec.rb'
7
+ t.verbose = false
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,77 @@
1
+ module LXC
2
+ class Configuration
3
+ include LXC::ConfigurationOptions
4
+
5
+ attr_reader :content
6
+
7
+ # Initialize a new LXC::Configuration instance
8
+ # @param [data] string or hash data (optional)
9
+ def initialize(data=nil)
10
+ if data.kind_of?(String)
11
+ @content = parse(data)
12
+ end
13
+ end
14
+
15
+ # Load an existing LXC container configuration from file
16
+ # @param [path] path to configuration
17
+ # @return [LXC::Configuration]
18
+ def self.load_file(path)
19
+ fullpath = File.expand_path(path)
20
+ if !File.exists?(fullpath)
21
+ raise ArgumentError, "File '#{path}' does not exist."
22
+ end
23
+ LXC::Configuration.new(File.read(fullpath))
24
+ end
25
+
26
+ # Get all configuration attributes
27
+ # @return [Array]
28
+ def attributes
29
+ @content.keys
30
+ end
31
+
32
+ # Get attribute value
33
+ # @param [key] attribute name
34
+ # @return [String] configuration attribute value
35
+ def [](key)
36
+ @content[key.to_s]
37
+ end
38
+
39
+ def method_missing(key)
40
+ @content[key.to_s]
41
+ end
42
+
43
+ # Save configuration into file
44
+ # @param [path] path to output file
45
+ def save_to_file(path)
46
+ fullpath = File.expand_path(path)
47
+ lines = []
48
+ @content.each_pair do |key,value|
49
+ k = "lxc.#{key.gsub('_', '.')}"
50
+ if value.kind_of?(Array)
51
+ lines << value.map { |v| "#{k} = #{v}" }
52
+ else
53
+ lines << "#{k} = #{value}"
54
+ end
55
+ end
56
+ File.open(path, 'w') { |f| f.write(lines.flatten.join("\n")) }
57
+ end
58
+
59
+ private
60
+
61
+ def parse(data)
62
+ hash = {}
63
+ lines = data.split("\n").map(&:strip).select { |l| !l.empty? && l[0,1] != '#' }
64
+ lines.each do |l|
65
+ key,value = l.split('=').map(&:strip)
66
+ if !valid_option?(key)
67
+ raise ConfigurationError, "Invalid config attribute: #{key}."
68
+ end
69
+ key.gsub!(/^lxc\./, '').gsub!('.', '_')
70
+ hash[key] = [] if !hash.key?(key)
71
+ hash[key] << value
72
+ end
73
+ hash.each_pair { |k,v| hash[k] = v.first if v.size == 1 }
74
+ hash
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,27 @@
1
+ module LXC
2
+ module ConfigurationOptions
3
+ VALID_OPTIONS = [
4
+ 'lxc.utsname',
5
+ 'lxc.network.type',
6
+ 'lxc.network.flags',
7
+ 'lxc.network.link',
8
+ 'lxc.network.name',
9
+ 'lxc.network.hwaddr',
10
+ 'lxc.network.ipv4',
11
+ 'lxc.network.ipv6',
12
+ 'lxc.pts',
13
+ 'lxc.tty',
14
+ 'lxc.mount',
15
+ 'lxc.mount.entry',
16
+ 'lxc.rootfs',
17
+ 'lxc.cgroup',
18
+ 'lxc.cap.drop'
19
+ ]
20
+
21
+ protected
22
+
23
+ def valid_option?(name)
24
+ VALID_OPTIONS.include?(name) || name =~ /^lxc.cgroup/
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,219 @@
1
+ module LXC
2
+ class Container
3
+ attr_accessor :name
4
+ attr_reader :state
5
+ attr_reader :pid
6
+
7
+ # Initialize a new LXC::Container instance
8
+ # @param [String] name container name
9
+ # @return [LXC::Container] container instance
10
+ def initialize(name)
11
+ @name = name
12
+ end
13
+
14
+ # Get container attributes hash
15
+ # @return [Hash]
16
+ def to_hash
17
+ status
18
+ {'name' => name, 'state' => state, 'pid' => pid}
19
+ end
20
+
21
+ # Get current status of container
22
+ # @return [Hash] hash with :state and :pid attributes
23
+ def status
24
+ str = LXC.run('info', '-n', name)
25
+ @state = str.scan(/^state:\s+([\w]+)$/).flatten.first
26
+ @pid = str.scan(/^pid:\s+(-?[\d]+)$/).flatten.first
27
+ {:state => @state, :pid => @pid}
28
+ end
29
+
30
+ # Check if container exists
31
+ # @return [Boolean]
32
+ def exists?
33
+ LXC.run('ls').split("\n").uniq.include?(name)
34
+ end
35
+
36
+ # Check if container is running
37
+ # @return [Boolean]
38
+ def running?
39
+ status[:state] == 'RUNNING'
40
+ end
41
+
42
+ # Check if container is frozen
43
+ # @return [Boolean]
44
+ def frozen?
45
+ status[:state] == 'FROZEN'
46
+ end
47
+
48
+ # Start container
49
+ # @return [Hash] container status hash
50
+ def start
51
+ LXC.run('start', '-d', '-n', name)
52
+ status
53
+ end
54
+
55
+ # Stop container
56
+ # @return [Hash] container status hash
57
+ def stop
58
+ LXC.run('stop', '-n', name)
59
+ status
60
+ end
61
+
62
+ # Restart container
63
+ # @return [Hash] container status hash
64
+ def restart
65
+ stop
66
+ start
67
+ end
68
+
69
+ # Freeze container
70
+ # @return [Hash] container status hash
71
+ def freeze
72
+ LXC.run('freeze', '-n', name)
73
+ status
74
+ end
75
+
76
+ # Unfreeze container
77
+ # @return [Hash] container status hash
78
+ def unfreeze
79
+ LXC.run('unfreeze', '-n', name)
80
+ status
81
+ end
82
+
83
+ # Wait for container to change status
84
+ # @param [String] state state name
85
+ def wait(state)
86
+ if !LXC::Shell.valid_state?(state)
87
+ raise ArgumentError, "Invalid container state: #{state}"
88
+ end
89
+ LXC.run('wait', '-n', name, '-s', state)
90
+ end
91
+
92
+ # Get container memory usage in bytes
93
+ # @return [Integer]
94
+ def memory_usage
95
+ LXC.run('cgroup', '-n', name, 'memory.usage_in_bytes').strip.to_i
96
+ end
97
+
98
+ # Get container memory limit in bytes
99
+ # @return [Integer]
100
+ def memory_limit
101
+ LXC.run('cgroup', '-n', name, 'memory.limit_in_bytes').strip.to_i
102
+ end
103
+
104
+ # Get container processes
105
+ # @return [Array] list of all processes
106
+ def processes
107
+ raise ContainerError, "Container is not running" if !running?
108
+ str = LXC.run('ps', '-n', name, '--', '-eo pid,user,%cpu,%mem,args').strip
109
+ lines = str.split("\n") ; lines.delete_at(0)
110
+ lines.map { |l| parse_process_line(l) }
111
+ end
112
+
113
+ # Create a new container
114
+ # @param [String] path path to container config file or [Hash] options
115
+ # @return [Boolean]
116
+ def create(path)
117
+ raise ContainerError, "Container already exists." if exists?
118
+ if path.is_a?(Hash)
119
+ args = "-n #{name}"
120
+
121
+ if !!path[:config_file]
122
+ unless File.exists?(path[:config_file])
123
+ raise ArgumentError, "File #{path[:config_file]} does not exist."
124
+ end
125
+ args += " -f #{path[:config_file]}"
126
+ end
127
+
128
+ if !!path[:template]
129
+ template_path = "/usr/lib/lxc/templates/lxc-#{path[:template]}"
130
+ unless File.exists?(template_path)
131
+ raise ArgumentError, "Template #{path[:template]} does not exist."
132
+ end
133
+ args += " -t #{path[:template]}"
134
+ end
135
+
136
+ args += " -B #{path[:backingstore]}" if !!path[:backingstore]
137
+ args += " -- #{path[:template_options].join(' ')}".strip if !!path[:template_options]
138
+
139
+ LXC.run('create', args)
140
+ exists?
141
+ else
142
+ raise ArgumentError, "File #{path} does not exist." unless File.exists?(path)
143
+ LXC.run('create', '-n', name, '-f', path)
144
+ exists?
145
+ end
146
+ end
147
+
148
+ # Clone to a new container from self
149
+ # @param [String] target name of new container
150
+ # @return [LXC::Container] new container instance
151
+ def clone_to(target)
152
+ raise ContainerError, "Container does not exist." unless exists?
153
+ if self.class.new(target).exists?
154
+ raise ContainerError, "New container already exists."
155
+ end
156
+
157
+ LXC.run('clone', '-o', name, '-n', target)
158
+ self.class.new target
159
+ end
160
+
161
+ # Create a new container from an existing container
162
+ # @param [String] source name of existing container
163
+ # @return [Boolean]
164
+ def clone_from(source)
165
+ raise ContainerError, "Container already exists." if exists?
166
+ unless self.class.new(source).exists?
167
+ raise ContainerError, "Source container does not exist."
168
+ end
169
+
170
+ LXC.run('clone', '-o', source, '-n', name)
171
+ exists?
172
+ end
173
+
174
+ # Destroy the container
175
+ # @param [Boolean] force force destruction
176
+ # @return [Boolean] true if container was destroyed
177
+ #
178
+ # If container is running and `force` parameter is true
179
+ # it will be stopped first. Otherwise it will raise exception.
180
+ #
181
+ def destroy(force=false)
182
+ raise ContainerError, "Container does not exist." unless exists?
183
+ if running?
184
+ if force
185
+ # This will force stop and destroy container automatically
186
+ LXC.run('destroy', '-n', '-f', name)
187
+ else
188
+ raise ContainerError, "Container is running. Stop it first or use force=true"
189
+ end
190
+ else
191
+ LXC.run('destroy', '-n', name)
192
+ end
193
+ !exists?
194
+ end
195
+
196
+ private
197
+
198
+ def parse_process_line(line)
199
+ chunks = line.split(' ')
200
+ chunks.delete_at(0)
201
+
202
+ pid = chunks.shift
203
+ user = chunks.shift
204
+ cpu = chunks.shift
205
+ mem = chunks.shift
206
+ command = chunks.shift
207
+ args = chunks.join(' ')
208
+
209
+ {
210
+ 'pid' => pid,
211
+ 'user' => user,
212
+ 'cpu' => cpu,
213
+ 'memory' => mem,
214
+ 'command' => command,
215
+ 'args' => args
216
+ }
217
+ end
218
+ end
219
+ end
data/lib/lxc/errors.rb ADDED
@@ -0,0 +1,5 @@
1
+ module LXC
2
+ class Error < StandardError ; end
3
+ class ContainerError < Error ; end
4
+ class ConfigurationError < Error ; end
5
+ end
data/lib/lxc/shell.rb ADDED
@@ -0,0 +1,89 @@
1
+ module LXC
2
+ module Shell
3
+ extend self
4
+
5
+ BIN_PREFIX = '/usr/bin'
6
+
7
+ BIN_FILES = [
8
+ 'lxc-attach',
9
+ 'lxc-cgroup',
10
+ 'lxc-checkconfig',
11
+ 'lxc-checkpoint',
12
+ 'lxc-clone',
13
+ 'lxc-console',
14
+ 'lxc-create',
15
+ 'lxc-destroy',
16
+ 'lxc-execute',
17
+ 'lxc-freeze',
18
+ 'lxc-info',
19
+ 'lxc-kill',
20
+ 'lxc-ls',
21
+ 'lxc-monitor',
22
+ 'lxc-netstat',
23
+ 'lxc-ps',
24
+ 'lxc-restart',
25
+ 'lxc-setcap',
26
+ 'lxc-setuid',
27
+ 'lxc-start',
28
+ 'lxc-start-ephemeral',
29
+ 'lxc-stop',
30
+ 'lxc-unfreeze',
31
+ 'lxc-unshare',
32
+ 'lxc-version',
33
+ 'lxc-wait'
34
+ ]
35
+
36
+ CONTAINER_STATES = [
37
+ 'STOPPED',
38
+ 'STARTING',
39
+ 'RUNNING',
40
+ 'STOPPING',
41
+ 'ABORTING',
42
+ 'FREEZING',
43
+ 'FROZEN'
44
+ ]
45
+
46
+ @@use_sudo = false
47
+
48
+ # Check if LXC is using sudo to run commands
49
+ # @return [Boolean] current sudo flag value
50
+ def use_sudo
51
+ @@use_sudo
52
+ end
53
+
54
+ # Set LXC to execute commands with sudo
55
+ # @param val [Boolean] true for sudo usage
56
+ # @return [Boolean] new sudo flag value
57
+ def use_sudo=(val)
58
+ @@use_sudo = val
59
+ val
60
+ end
61
+
62
+ # Check if container state is valid
63
+ # @param name [String] container name
64
+ # @return [Boolean]
65
+ def valid_state?(name)
66
+ CONTAINER_STATES.include?(name)
67
+ end
68
+
69
+ # Execute a LXC command
70
+ # @param [String] name command name
71
+ # @param [Array] args command arguments
72
+ # @return [String] execution result
73
+ #
74
+ # If you would like to use pipe command you'll need to
75
+ # provide a block that returns string
76
+ def run(command, *args)
77
+ command_name = "lxc-#{command}"
78
+ unless BIN_FILES.include?(command_name)
79
+ raise ArgumentError, "Invalid command: #{command_name}."
80
+ end
81
+
82
+ cmd = ""
83
+ cmd += "sudo " if use_sudo == true
84
+ cmd += "#{command_name} #{args.join(' ')}".strip
85
+ cmd += " | #{yield}" if block_given?
86
+ `#{cmd.strip}`
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module LXC
2
+ VERSION = '0.2.0'
3
+ end
data/lib/lxc.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'lxc/version'
2
+ require 'lxc/errors'
3
+ require 'lxc/shell'
4
+ require 'lxc/configuration_options'
5
+ require 'lxc/configuration'
6
+ require 'lxc/container'
7
+
8
+ module LXC
9
+ class << self
10
+ include LXC::Shell
11
+
12
+ # Check if binary file is installed
13
+ # @param [String] name binary filename
14
+ # @return [Boolean] true if installed
15
+ def binary_installed?(name)
16
+ path = File.join(LXC::Shell::BIN_PREFIX, name)
17
+ File.exists?(path)
18
+ end
19
+
20
+ # Check if all binaries are present in the system
21
+ # @return [Boolean] true if binary files are found
22
+ def installed?
23
+ !BIN_FILES.map { |f| binary_installed?(f) }.uniq.include?(false)
24
+ end
25
+
26
+ # Get LXC configuration info
27
+ # @return [Hash] hash containing config groups
28
+ def config
29
+ str = LXC.run('checkconfig') { 'sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g"' }
30
+ data = str.scan(/^([\w\s]+): (enabled|disabled)$/).map { |r|
31
+ [r.first.downcase.gsub(' ', '_'), r.last == 'enabled']
32
+ }
33
+ Hash[data]
34
+ end
35
+
36
+ # Get container information record
37
+ # @param [name] name container name
38
+ # @return [LXC::Container] container instance
39
+ def container(name)
40
+ LXC::Container.new(name)
41
+ end
42
+
43
+ # Get a list of all available containers
44
+ # @param [String] filter select containers that match string
45
+ # @return [Array] array of LXC::Containers
46
+ def containers(filter=nil)
47
+ names = LXC.run('ls').split("\n").uniq
48
+ names.delete_if { |v| !v.include?(filter) } if filter.kind_of?(String)
49
+ names.map { |name| Container.new(name) }
50
+ end
51
+
52
+ # Get current LXC version
53
+ # @return [String] current LXC version
54
+ def version
55
+ LXC.run('version').strip.split(' ').last
56
+ end
57
+ end
58
+ end
data/lxc.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ require File.expand_path('../lib/lxc/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "lxc-ruby"
5
+ s.version = LXC::VERSION
6
+ s.summary = "Ruby wrapper to LXC"
7
+ s.description = "Ruby wrapper to manage LXC (Linux Containers)."
8
+ s.homepage = "http://github.com/sosedoff/lxc"
9
+ s.authors = ["Dan Sosedoff"]
10
+ s.email = ["dan.sosedoff@gmail.com"]
11
+
12
+ s.add_development_dependency 'rake'
13
+ s.add_development_dependency 'rspec', '~> 2.6'
14
+ s.add_development_dependency 'simplecov', '~> 0.4'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe LXC::Configuration do
4
+ it 'parses config data' do
5
+ conf = LXC::Configuration.new(fixture('configuration.txt'))
6
+ conf.content.should be_a Hash
7
+ conf.attributes.should be_an Array
8
+ conf.attributes.should_not be_empty
9
+ conf.utsname.should_not be_nil
10
+ conf['utsname'].should_not be_nil
11
+ conf[:utsname].should_not be_nil
12
+ conf.network_ipv4.should_not be_nil
13
+ end
14
+
15
+ it 'saves config data into file' do
16
+ conf = LXC::Configuration.new(fixture('configuration.txt'))
17
+ conf.save_to_file('/tmp/lxc.txt')
18
+ c1 = LXC::Configuration.new(fixture('configuration.txt'))
19
+ c2 = LXC::Configuration.new(File.read('/tmp/lxc.txt'))
20
+ c1.content.should eq(c2.content)
21
+ end
22
+ end
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe LXC::Container do
4
+ subject { LXC::Container.new('app') }
5
+
6
+ it { should respond_to(:name) }
7
+ it { should respond_to(:state) }
8
+ it { should respond_to(:pid) }
9
+
10
+ it 'has proper default attributes' do
11
+ subject.name.should eq('app')
12
+ subject.state.should be_nil
13
+ subject.pid.should be_nil
14
+ end
15
+
16
+ it 'should exist' do
17
+ stub_lxc('ls') { "app\napp2" }
18
+ subject.exists?.should be_true
19
+
20
+ stub_lxc('ls') { "app2\napp3" }
21
+ subject.exists?.should be_false
22
+ end
23
+
24
+ context '.status' do
25
+ it 'returns STOPPED' do
26
+ stub_lxc('info', '-n', 'app') { fixture('lxc-info-stopped.txt') }
27
+ subject.status.should eq({:state => 'STOPPED', :pid => '-1'})
28
+ end
29
+
30
+ it 'returns RUNNING' do
31
+ stub_lxc('info', '-n', 'app') { fixture('lxc-info-running.txt') }
32
+ subject.status.should eq({:state => 'RUNNING', :pid => '2125'})
33
+ end
34
+ end
35
+
36
+ context '.destroy' do
37
+ it 'raises error if container does not exist' do
38
+ stub_lxc('ls') { "app2" }
39
+ proc { subject.destroy }.
40
+ should raise_error LXC::ContainerError, "Container does not exist."
41
+ end
42
+
43
+ it 'raises error if container is running' do
44
+ stub_lxc('ls') { "app" }
45
+ stub_lxc('info', '-n', 'app') { fixture('lxc-info-running.txt') }
46
+ proc { subject.destroy }.
47
+ should raise_error LXC::ContainerError, "Container is running. Stop it first or use force=true"
48
+ end
49
+ end
50
+
51
+ it 'returns the amount of used memory' do
52
+ stub_lxc('cgroup', '-n', 'app', 'memory.usage_in_bytes') { "3280896\n" }
53
+ subject.memory_usage.should eq(3280896)
54
+ end
55
+
56
+ it 'returns the memory limit' do
57
+ stub_lxc('cgroup', '-n', 'app', 'memory.limit_in_bytes') { "268435456\n" }
58
+ subject.memory_limit.should eq(268435456)
59
+ end
60
+
61
+ context '.processes' do
62
+ it 'raises error if container is not running' do
63
+ stub_lxc('info', '-n', 'app') { fixture('lxc-info-stopped.txt') }
64
+
65
+ proc { subject.processes }.
66
+ should raise_error LXC::ContainerError, "Container is not running"
67
+ end
68
+
69
+ it 'returns list of all processes' do
70
+ stub_lxc('info', '-n', 'app') { fixture('lxc-info-running.txt') }
71
+ stub_lxc('ps', '-n', 'app', '--', '-eo pid,user,%cpu,%mem,args') { fixture('lxc-ps-aux.txt') }
72
+
73
+ list = subject.processes
74
+ list.should be_an Array
75
+
76
+ p = list.first
77
+ p.should be_a Hash
78
+ p.should have_key('pid')
79
+ p.should have_key('user')
80
+ p.should have_key('cpu')
81
+ p.should have_key('memory')
82
+ p.should have_key('command')
83
+ p.should have_key('args')
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,33 @@
1
+ lxc.utsname = NAME
2
+
3
+ lxc.tty = 4
4
+ lxc.pts = 1024
5
+ lxc.rootfs = ROOTFS
6
+ lxc.mount = ROOTFS/fstab
7
+
8
+ lxc.network.type = veth
9
+ lxc.network.flags = up
10
+ lxc.network.name = eth0
11
+ lxc.network.link = br0
12
+ lxc.network.ipv4 = 0.0.0.0
13
+
14
+ lxc.cgroup.devices.deny = a
15
+
16
+ # /dev/null and zero
17
+ lxc.cgroup.devices.allow = c 1:3 rwm
18
+ lxc.cgroup.devices.allow = c 1:5 rwm
19
+
20
+ # consoles
21
+ lxc.cgroup.devices.allow = c 5:1 rwm
22
+ lxc.cgroup.devices.allow = c 5:0 rwm
23
+ lxc.cgroup.devices.allow = c 4:0 rwm
24
+ lxc.cgroup.devices.allow = c 4:1 rwm
25
+
26
+ # /dev/{,u}random
27
+ lxc.cgroup.devices.allow = c 1:9 rwm
28
+ lxc.cgroup.devices.allow = c 1:8 rwm
29
+ lxc.cgroup.devices.allow = c 136:* rwm
30
+ lxc.cgroup.devices.allow = c 5:2 rwm
31
+
32
+ # rtc
33
+ lxc.cgroup.devices.allow = c 254:0 rwm
@@ -0,0 +1,29 @@
1
+ Kernel config /proc/config.gz not found, looking in other places...
2
+ Found kernel config file /boot/config-3.0.0-12-server
3
+ --- Namespaces ---
4
+ Namespaces: enabled
5
+ Utsname namespace: enabled
6
+ Ipc namespace: enabled
7
+ Pid namespace: enabled
8
+ User namespace: enabled
9
+ Network namespace: enabled
10
+ Multiple /dev/pts instances: enabled
11
+
12
+ --- Control groups ---
13
+ Cgroup: enabled
14
+ Cgroup clone_children flag: enabled
15
+ Cgroup device: enabled
16
+ Cgroup sched: enabled
17
+ Cgroup cpu account: enabled
18
+ Cgroup memory controller: enabled
19
+ Cgroup cpuset: enabled
20
+
21
+ --- Misc ---
22
+ Veth pair device: enabled
23
+ Macvlan: enabled
24
+ Vlan: enabled
25
+ File capabilities: enabled
26
+
27
+ Note : Before booting a new kernel, you can check its configuration
28
+ usage : CONFIG=/path/to/config /usr/bin/lxc-checkconfig
29
+
@@ -0,0 +1,2 @@
1
+ state: RUNNING
2
+ pid: 2125
@@ -0,0 +1,2 @@
1
+ state: STOPPED
2
+ pid: -1
@@ -0,0 +1,9 @@
1
+ CONTAINER PID USER %CPU %MEM COMMAND
2
+ app 27404 root 0.0 0.1 /sbin/init
3
+ app 27476 root 0.0 0.0 upstart-udev-bridge --daemon
4
+ app 27479 root 0.0 0.1 udevd --daemon
5
+ app 27590 root 0.0 0.0 udevd --daemon
6
+ app 27591 root 0.0 0.0 udevd --daemon
7
+ app 27692 root 0.0 0.0 upstart-socket-bridge --daemon
8
+ app 27704 root 0.0 0.2 /usr/sbin/sshd -D
9
+ app 27784 root 0.0 0.0 /sbin/getty -8 38400 console
@@ -0,0 +1 @@
1
+ lxc version: 0.7.5
data/spec/lxc_spec.rb ADDED
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe LXC do
5
+ describe '.check_binaries' do
6
+ LXC::Shell::BIN_PREFIX = '/tmp/lxc'
7
+
8
+ before do
9
+ FileUtils.mkdir_p('/tmp/lxc')
10
+ LXC::Shell::BIN_FILES.each do |f|
11
+ FileUtils.touch("/tmp/lxc/#{f}")
12
+ FileUtils.chmod(0555, "/tmp/lxc/#{f}")
13
+ end
14
+ end
15
+
16
+ after do
17
+ FileUtils.rm_rf('/tmp/lxc')
18
+ end
19
+
20
+ it 'returns true if all files are found' do
21
+ LXC.installed?.should be_true
22
+ end
23
+
24
+ it 'returns false on missing files' do
25
+ FileUtils.rm("/tmp/lxc/lxc-version")
26
+ LXC.installed?.should be_false
27
+ end
28
+ end
29
+
30
+ it 'returns installed version' do
31
+ stub_lxc('version') { fixture('lxc-version.txt') }
32
+ LXC.version.should eq('0.7.5')
33
+ end
34
+
35
+ it 'returns config hash with attributes' do
36
+ stub_lxc('checkconfig') { fixture('lxc-checkconfig.txt') }
37
+
38
+ info = LXC.config
39
+ info.should be_a Hash
40
+
41
+ info['namespaces'].should be_true
42
+ info['utsname_namespace'].should be_true
43
+ info['ipc_namespace'].should be_true
44
+ info['pid_namespace'].should be_true
45
+ info['user_namespace'].should be_true
46
+ info['network_namespace'].should be_true
47
+ info['cgroup'].should be_true
48
+ info['cgroup_clone_children_flag'].should be_true
49
+ info['cgroup_device'].should be_true
50
+ info['cgroup_sched'].should be_true
51
+ info['cgroup_cpu_account'].should be_true
52
+ info['cgroup_memory_controller'].should be_true
53
+ info['cgroup_cpuset'].should be_true
54
+ info['veth_pair_device'].should be_true
55
+ info['macvlan'].should be_true
56
+ info['vlan'].should be_true
57
+ info['file_capabilities'].should be_true
58
+ end
59
+
60
+ it 'returns a single container' do
61
+ c = LXC.container('foo')
62
+ c.should be_a LXC::Container
63
+ c.name.should eq('foo')
64
+ end
65
+
66
+ it 'returns all available containers' do
67
+ stub_lxc('ls') { "vm0\nvm1\nvm0" }
68
+ list = LXC.containers
69
+ list.should be_an Array
70
+ list.size.should eq(2)
71
+ list.first.should be_a LXC::Container
72
+ list.first.name.should eq('vm0')
73
+ end
74
+
75
+ it 'returns filtered list of containers' do
76
+ stub_lxc('ls') { "vm0\nvm1\nfoo\n"}
77
+ list = LXC.containers("vm")
78
+ list.size.should eq(2)
79
+ end
80
+
81
+ context 'sudo' do
82
+ class Foo
83
+ include LXC::Shell
84
+ end
85
+
86
+ before do
87
+ LXC.use_sudo = true
88
+ end
89
+
90
+ it 'executes command using sudo' do
91
+ LXC.use_sudo.should be_true
92
+
93
+ bar = Foo.new
94
+ bar.should_receive(:'`').with('sudo lxc-version').and_return(fixture('lxc-version.txt'))
95
+ bar.run('version').should_not be_empty
96
+ end
97
+ end
98
+
99
+ context '.use_sudo' do
100
+ class Bar
101
+ include LXC::Shell
102
+ end
103
+
104
+ it 'should be true' do
105
+ foo = Bar.new
106
+ foo.use_sudo.should eq(true)
107
+ LXC.use_sudo.should eq(true)
108
+ end
109
+
110
+ it 'should be false' do
111
+ LXC.use_sudo = false
112
+ foo = Bar.new
113
+
114
+ LXC.use_sudo.should eq(false)
115
+ foo.use_sudo.should eq(false)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,17 @@
1
+ $:.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'lib/lxc'
4
+
5
+ def fixture_path(filename=nil)
6
+ path = File.expand_path("../fixtures", __FILE__)
7
+ filename.nil? ? path : File.join(path, filename)
8
+ end
9
+
10
+ def fixture(file)
11
+ File.read(File.join(fixture_path, file))
12
+ end
13
+
14
+ def stub_lxc(command, *args)
15
+ output = yield
16
+ LXC.should_receive(:run).with(command, *args).and_return(output)
17
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lxc-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dan Sosedoff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-28 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &2152032520 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2152032520
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &2152032020 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.6'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2152032020
36
+ - !ruby/object:Gem::Dependency
37
+ name: simplecov
38
+ requirement: &2152031520 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '0.4'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *2152031520
47
+ description: Ruby wrapper to manage LXC (Linux Containers).
48
+ email:
49
+ - dan.sosedoff@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rspec
56
+ - .travis.yml
57
+ - Gemfile
58
+ - README.md
59
+ - Rakefile
60
+ - lib/lxc.rb
61
+ - lib/lxc/configuration.rb
62
+ - lib/lxc/configuration_options.rb
63
+ - lib/lxc/container.rb
64
+ - lib/lxc/errors.rb
65
+ - lib/lxc/shell.rb
66
+ - lib/lxc/version.rb
67
+ - lxc.gemspec
68
+ - spec/configuration_spec.rb
69
+ - spec/container_spec.rb
70
+ - spec/fixtures/configuration.txt
71
+ - spec/fixtures/lxc-checkconfig.txt
72
+ - spec/fixtures/lxc-info-running.txt
73
+ - spec/fixtures/lxc-info-stopped.txt
74
+ - spec/fixtures/lxc-ps-aux.txt
75
+ - spec/fixtures/lxc-version.txt
76
+ - spec/lxc_spec.rb
77
+ - spec/spec_helper.rb
78
+ homepage: http://github.com/sosedoff/lxc
79
+ licenses: []
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 1.8.15
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Ruby wrapper to LXC
102
+ test_files:
103
+ - spec/configuration_spec.rb
104
+ - spec/container_spec.rb
105
+ - spec/fixtures/configuration.txt
106
+ - spec/fixtures/lxc-checkconfig.txt
107
+ - spec/fixtures/lxc-info-running.txt
108
+ - spec/fixtures/lxc-info-stopped.txt
109
+ - spec/fixtures/lxc-ps-aux.txt
110
+ - spec/fixtures/lxc-version.txt
111
+ - spec/lxc_spec.rb
112
+ - spec/spec_helper.rb
113
+ has_rdoc: