toft 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +57 -0
- data/Rakefile +1 -0
- data/features/checker.feature +14 -0
- data/features/chef.feature +52 -0
- data/features/command.feature +19 -0
- data/features/node.feature +21 -0
- data/features/step_definitions/checker.rb +15 -0
- data/features/step_definitions/chef.rb +35 -0
- data/features/step_definitions/command.rb +21 -0
- data/features/step_definitions/node.rb +38 -0
- data/features/support/env.rb +19 -0
- data/fixtures/chef/cookbooks/test/attributes/default.rb +4 -0
- data/fixtures/chef/cookbooks/test/recipes/attribute.rb +19 -0
- data/fixtures/chef/cookbooks/test/recipes/default.rb +4 -0
- data/fixtures/chef/cookbooks/test/recipes/role.rb +4 -0
- data/fixtures/chef/roles/test.rb +3 -0
- data/lib/toft/chef/chef_attributes.rb +29 -0
- data/lib/toft/chef/chef_runner.rb +64 -0
- data/lib/toft/file_checker.rb +42 -0
- data/lib/toft/node.rb +140 -0
- data/lib/toft/node_controller.rb +32 -0
- data/lib/toft/version.rb +3 -0
- data/lib/toft.rb +38 -0
- data/scripts/bash/install-chef-ubuntu.sh +11 -0
- data/scripts/bash/install-rvm.sh +79 -0
- data/scripts/cookbooks/lxc/attributes/default.rb +1 -0
- data/scripts/cookbooks/lxc/files/default/lxc-create-ubuntu-image +61 -0
- data/scripts/cookbooks/lxc/recipes/default.rb +42 -0
- data/scripts/cookbooks/lxc/templates/default/lxc-lucid-chef +328 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/tuft/chef_attributes_spec.rb +33 -0
- metadata +164 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
toft (0.0.1)
|
5
|
+
net-ssh
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
archive-tar-minitar (0.5.2)
|
11
|
+
builder (3.0.0)
|
12
|
+
cucumber (1.1.0)
|
13
|
+
builder (>= 2.1.2)
|
14
|
+
diff-lcs (>= 1.1.2)
|
15
|
+
gherkin (~> 2.5.0)
|
16
|
+
json (>= 1.4.6)
|
17
|
+
term-ansicolor (>= 1.0.6)
|
18
|
+
diff-lcs (1.1.3)
|
19
|
+
erubis (2.7.0)
|
20
|
+
ffi (1.0.9)
|
21
|
+
gherkin (2.5.1)
|
22
|
+
json (>= 1.4.6)
|
23
|
+
i18n (0.6.0)
|
24
|
+
json (1.5.4)
|
25
|
+
net-scp (1.0.4)
|
26
|
+
net-ssh (>= 1.99.1)
|
27
|
+
net-ssh (2.1.4)
|
28
|
+
rspec (2.6.0)
|
29
|
+
rspec-core (~> 2.6.0)
|
30
|
+
rspec-expectations (~> 2.6.0)
|
31
|
+
rspec-mocks (~> 2.6.0)
|
32
|
+
rspec-core (2.6.4)
|
33
|
+
rspec-expectations (2.6.0)
|
34
|
+
diff-lcs (~> 1.1.2)
|
35
|
+
rspec-mocks (2.6.0)
|
36
|
+
term-ansicolor (1.0.6)
|
37
|
+
thor (0.14.6)
|
38
|
+
vagrant (0.8.7)
|
39
|
+
archive-tar-minitar (= 0.5.2)
|
40
|
+
erubis (~> 2.7.0)
|
41
|
+
i18n (~> 0.6.0)
|
42
|
+
json (~> 1.5.1)
|
43
|
+
net-scp (~> 1.0.4)
|
44
|
+
net-ssh (~> 2.1.4)
|
45
|
+
thor (~> 0.14.6)
|
46
|
+
virtualbox (~> 0.9.1)
|
47
|
+
virtualbox (0.9.2)
|
48
|
+
ffi (~> 1.0.9)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
cucumber
|
55
|
+
rspec
|
56
|
+
toft!
|
57
|
+
vagrant (>= 0.8.7)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Feature: Checkers provided by Toft to help you verify system state
|
2
|
+
|
3
|
+
Scenario: Dir checker
|
4
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
5
|
+
Then Node "n1" should have "directory" "/tmp" owned by user "root" and group "root" with permission "1777"
|
6
|
+
Then Node "n1" should have not file or directory "/non-exist-dir"
|
7
|
+
Then Node "n1" should have file or directory "tmp"
|
8
|
+
|
9
|
+
Scenario: File checker
|
10
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
11
|
+
When Running ssh command "if getent passwd n1; then userdel n1; fi; useradd -m n1" on "n1" should succeed
|
12
|
+
And Running ssh command "sudo -u n1 touch /tmp/a" on "n1" should succeed
|
13
|
+
Then Node "n1" should have file or directory "/tmp/a"
|
14
|
+
And Node "n1" should have "regular empty file" "/tmp/a" owned by user "n1" and group "n1" with permission "644"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
Feature: Chef support
|
2
|
+
|
3
|
+
Scenario: Run chef recipe on nodes
|
4
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
5
|
+
When I run "recipe[test]" on node "n1"
|
6
|
+
Then Node "n1" should have file or directory "/tmp/stub/dir"
|
7
|
+
|
8
|
+
Scenario: Run chef recipe with attributes
|
9
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
10
|
+
When I run "recipe[test::attribute]" on node "n1" and overwrite attributes with:
|
11
|
+
|key|value|
|
12
|
+
|one|one|
|
13
|
+
|two.one|two_one|
|
14
|
+
|two.two|two_two|
|
15
|
+
|three|three|
|
16
|
+
Then Node "n1" should have file or directory "/tmp/stub/one"
|
17
|
+
Then Node "n1" should have file or directory "/tmp/stub/two_one"
|
18
|
+
Then Node "n1" should have file or directory "/tmp/stub/two_two"
|
19
|
+
Then Node "n1" should have file or directory "/tmp/stub/three"
|
20
|
+
|
21
|
+
Scenario: Run multiple chef recipes
|
22
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
23
|
+
When I run recipes on node "n1":
|
24
|
+
|recipe|
|
25
|
+
|recipe[test::role]|
|
26
|
+
|recipe[test]|
|
27
|
+
Then Node "n1" should have file or directory "/tmp/stub/dir"
|
28
|
+
Then Node "n1" should have file or directory "/tmp/stub/role"
|
29
|
+
|
30
|
+
Scenario: Run chef role
|
31
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
32
|
+
When I run "role[test]" on node "n1"
|
33
|
+
Then Node "n1" should have file or directory "/tmp/stub/role"
|
34
|
+
|
35
|
+
Scenario: Toft should not deal with empty cookbook and role path
|
36
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
37
|
+
When I set the role path to empty
|
38
|
+
Then Running chef "recipe[test]" on node "n1" should succeed
|
39
|
+
When I set the cookbook path to empty
|
40
|
+
Then Running chef "recipe[test]" on node "n1" should fail
|
41
|
+
|
42
|
+
Scenario: Run non-exist recipe
|
43
|
+
|
44
|
+
Scenario: Run non-exist role
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Feature: Run ssh command on node
|
2
|
+
|
3
|
+
Scenario: Run command on node successfully
|
4
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
5
|
+
Then Running ssh command "" on "n1" should fail
|
6
|
+
And Running ssh command "ps" on "n1" should succeed
|
7
|
+
And Running ssh command "non-exist-command" on "n1" should fail
|
8
|
+
And Running ssh command "netstat -nr" on "n1" should succeed
|
9
|
+
|
10
|
+
Scenario: Test rm
|
11
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
12
|
+
Then Rm "" on "n1" should fail
|
13
|
+
And Rm "tmp/*" on "n1" should fail
|
14
|
+
And Rm "/tmp/*" on "n1" should succeed
|
15
|
+
|
16
|
+
Scenario: Check ssh command result
|
17
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
18
|
+
Then the result of running ssh command "ps" on "n1" should contain "sshd"
|
19
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Feature: Node management
|
2
|
+
|
3
|
+
Scenario: Start or stop node
|
4
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
5
|
+
Then the node "n1" should be running
|
6
|
+
When I stop node "n1"
|
7
|
+
Then the node "n1" should be stopped
|
8
|
+
When I start node "n1"
|
9
|
+
Then the node "n1" should be running
|
10
|
+
|
11
|
+
Scenario: Create or destroy node
|
12
|
+
Given I have a clean running node "n1" with ip "192.168.20.2"
|
13
|
+
Then There should be 1 nodes in the environment
|
14
|
+
When I add another node "n2" with ip "192.168.20.3"
|
15
|
+
Then There should be 2 nodes in the environment
|
16
|
+
When I destroy node "n2"
|
17
|
+
Then There should be 1 nodes in the environment
|
18
|
+
When Node "n1" is destroyed
|
19
|
+
Then There should be 0 nodes in the environment
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Then /^Node "([^"]*)" should have not file or directory "([^"]*)"$/ do |node, dirpath|
|
2
|
+
find(node).file(dirpath).should_not be_exist
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^Node "([^"]*)" should have file or directory "([^"]*)"$/ do |node, dirpath|
|
6
|
+
find(node).file(dirpath).should be_exist
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^Node "([^"]*)" should have "([^"]*)" "([^"]*)" owned by user "([^"]*)" and group "([^"]*)" with permission "([^"]*)"$/ do |node, type, dirpath, user, group, mode|
|
10
|
+
file = find(node).file(dirpath)
|
11
|
+
file.filetype.should == type
|
12
|
+
file.owner.should == user
|
13
|
+
file.group.should == group
|
14
|
+
file.mode.should == mode
|
15
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
When /^I run "([^"]*)" on node "([^"]*)"$/ do |run_list, node|
|
2
|
+
find(node).run_chef run_list
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I run recipes on node "([^"]*)":$/ do |node, recipes_table|
|
6
|
+
recipes = []
|
7
|
+
recipes_table.hashes.each do |row|
|
8
|
+
recipes << row[:recipe]
|
9
|
+
end
|
10
|
+
find(node).run_chef recipes
|
11
|
+
end
|
12
|
+
|
13
|
+
When /^I run "([^"]*)" on node "([^"]*)" and overwrite attributes with:$/ do |run_list, node, table|
|
14
|
+
find(node).run_chef run_list, Toft::ChefAttributes.new(table)
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^I set the role path to empty$/ do
|
18
|
+
Toft.role_path = ""
|
19
|
+
@n1.rm "/tmp/*"
|
20
|
+
end
|
21
|
+
|
22
|
+
Then /^Running chef "([^"]*)" on node "([^"]*)" should succeed$/ do |run_list, node|
|
23
|
+
result = false
|
24
|
+
lambda { result = find(node).run_chef(run_list) }.should_not raise_error
|
25
|
+
result.should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
When /^I set the cookbook path to empty$/ do
|
29
|
+
Toft.cookbook_path = ""
|
30
|
+
@n1.rm "/tmp/*"
|
31
|
+
end
|
32
|
+
|
33
|
+
Then /^Running chef "([^"]*)" on node "([^"]*)" should fail$/ do |run_list, node|
|
34
|
+
lambda { find(node).run_chef(run_list) }.should raise_error
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Then /^Running ssh command "([^"]*)" on "([^"]*)" should succeed$/ do |cmd, node|
|
2
|
+
find(node).run_ssh(cmd).should be_true
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^Running ssh command "([^"]*)" on "([^"]*)" should fail$/ do |cmd, node|
|
6
|
+
lambda { find(node).run_ssh(cmd) }.should raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^Rm "([^"]*)" on "([^"]*)" should fail$/ do |dir, node|
|
10
|
+
lambda { find(node).rm(dir) }.should raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
Then /^Rm "([^"]*)" on "([^"]*)" should succeed$/ do |dir, node|
|
14
|
+
lambda { find(node).rm(dir) }.should_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
Then /^the result of running ssh command "([^"]*)" on "([^"]*)" should contain "([^"]*)"$/ do |cmd, node, s|
|
18
|
+
find(node).run_ssh(cmd) do |output|
|
19
|
+
output.should include(s)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Given /^I have a clean running node "([^"]*)" with ip "([^"]*)"$/ do |node, ip|
|
2
|
+
create_node node, ip
|
3
|
+
@n1.start
|
4
|
+
@n1.rm "/tmp/stub"
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^I add another node "([^"]*)" with ip "([^"]*)"$/ do |node, ip|
|
8
|
+
create_node node, ip
|
9
|
+
end
|
10
|
+
|
11
|
+
When /^I destroy node "([^"]*)"$/ do |node|
|
12
|
+
destroy_node node
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^Node "([^"]*)" is destroyed$/ do |node|
|
16
|
+
find(node).destroy
|
17
|
+
end
|
18
|
+
|
19
|
+
Then /^There should be ([^"]*) nodes in the environment$/ do |count|
|
20
|
+
node_count.should == count.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
Then /^the node "([^"]*)" should be stopped$/ do |node|
|
24
|
+
find(node).should_not be_running
|
25
|
+
end
|
26
|
+
|
27
|
+
When /^I start node "([^"]*)"$/ do |node|
|
28
|
+
find(node).start
|
29
|
+
end
|
30
|
+
|
31
|
+
When /^I stop node "([^"]*)"$/ do |node|
|
32
|
+
find(node).stop
|
33
|
+
end
|
34
|
+
|
35
|
+
Then /^the node "([^"]*)" should be running$/ do |node|
|
36
|
+
find(node).should be_running
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
|
+
require 'rspec/expectations'
|
3
|
+
require 'toft'
|
4
|
+
|
5
|
+
Toft.cookbook_path = File.dirname(__FILE__) + '/../../fixtures/chef/cookbooks'
|
6
|
+
Toft.role_path = File.dirname(__FILE__) + '/../../fixtures/chef/roles'
|
7
|
+
|
8
|
+
World(Toft)
|
9
|
+
|
10
|
+
include Toft
|
11
|
+
n1 = create_node "n1", "192.168.20.2"
|
12
|
+
|
13
|
+
Before do
|
14
|
+
@n1 = n1
|
15
|
+
end
|
16
|
+
|
17
|
+
at_exit do
|
18
|
+
n1.destroy
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
directory "/tmp/stub/#{node.one}" do
|
2
|
+
action :create
|
3
|
+
recursive true
|
4
|
+
end
|
5
|
+
|
6
|
+
directory "/tmp/stub/#{node.two.two}" do
|
7
|
+
action :create
|
8
|
+
recursive true
|
9
|
+
end
|
10
|
+
|
11
|
+
directory "/tmp/stub/#{node.two.one}" do
|
12
|
+
action :create
|
13
|
+
recursive true
|
14
|
+
end
|
15
|
+
|
16
|
+
directory "/tmp/stub/#{node.three}" do
|
17
|
+
action :create
|
18
|
+
recursive true
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Toft
|
4
|
+
class ChefAttributes
|
5
|
+
attr_reader :attributes
|
6
|
+
|
7
|
+
def initialize(cuke_ast_table)
|
8
|
+
@attributes = {}
|
9
|
+
cuke_ast_table.hashes.each do |row|
|
10
|
+
add_attribute row[:key], row[:value]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_attribute(key, value)
|
15
|
+
stat = "attributes"
|
16
|
+
head = attributes
|
17
|
+
key.split(".").each do |k|
|
18
|
+
head[k] ||= Hash.new
|
19
|
+
head = head[k]
|
20
|
+
stat += "[\"#{k}\"]"
|
21
|
+
end
|
22
|
+
eval "#{stat}=\"#{value}\""
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json
|
26
|
+
attributes.to_json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'toft/chef/chef_attributes'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Toft
|
5
|
+
module Chef
|
6
|
+
class ChefRunner
|
7
|
+
include FileUtils
|
8
|
+
|
9
|
+
DEST_CHEF_TMP = "/tmp/toft-chef-tmp"
|
10
|
+
DEST_COOKBOOK_PATH = "#{DEST_CHEF_TMP}/cookbooks"
|
11
|
+
DEST_ROLE_PATH = "#{DEST_CHEF_TMP}/roles"
|
12
|
+
DEST_CHEF_SOLO_PATH = "#{DEST_CHEF_TMP}/solo.rb"
|
13
|
+
DEST_CHEF_JSON_PATH = "#{DEST_CHEF_TMP}/solo.json"
|
14
|
+
|
15
|
+
def initialize(root_dir, &command_runner)
|
16
|
+
@root_dir = root_dir
|
17
|
+
@command_runner = command_runner
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(run_list, chef_attributes)
|
21
|
+
copy_chef_material
|
22
|
+
generate_solo_rb
|
23
|
+
generate_json ([] << run_list).flatten, chef_attributes
|
24
|
+
@command_runner.call "chef-solo -c #{DEST_CHEF_SOLO_PATH} -j #{DEST_CHEF_JSON_PATH}"
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def copy_chef_material
|
29
|
+
rm_rf "#{@root_dir}#{DEST_CHEF_TMP}"
|
30
|
+
mkdir_p "#{@root_dir}#{DEST_CHEF_TMP}"
|
31
|
+
cp_r Toft.cookbook_path, "#{@root_dir}#{DEST_COOKBOOK_PATH}"
|
32
|
+
cp_r Toft.role_path, "#{@root_dir}#{DEST_ROLE_PATH}" unless roles_missing?
|
33
|
+
end
|
34
|
+
|
35
|
+
def roles_missing?
|
36
|
+
Toft.role_path.blank?
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate_solo_rb
|
40
|
+
solo = <<-EOF
|
41
|
+
file_cache_path "/tmp/chef-file-cache"
|
42
|
+
cookbook_path ["#{DEST_COOKBOOK_PATH}"]
|
43
|
+
EOF
|
44
|
+
solo += "role_path [\"#{DEST_ROLE_PATH}\"]" unless roles_missing?
|
45
|
+
|
46
|
+
File.open("#{@root_dir}#{DEST_CHEF_SOLO_PATH}", 'w') do |f|
|
47
|
+
f.write(solo);
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_json(run_list, chef_attributes)
|
52
|
+
run_list = {"run_list" => run_list}
|
53
|
+
run_list.merge!(chef_attributes.attributes) unless chef_attributes.nil?
|
54
|
+
File.open("#{@root_dir}#{DEST_CHEF_JSON_PATH}", 'w') do |f|
|
55
|
+
f.write(run_list.to_json);
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def cmd(cmd)
|
60
|
+
raise "Command execution failed: [#{cmd}]" unless system cmd
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Toft
|
2
|
+
class FileChecker
|
3
|
+
def initialize(rootfs, path)
|
4
|
+
@rootfs = rootfs
|
5
|
+
@path = path
|
6
|
+
end
|
7
|
+
|
8
|
+
def exist?
|
9
|
+
test("-e")
|
10
|
+
end
|
11
|
+
|
12
|
+
def directory?
|
13
|
+
filetype == "directory"
|
14
|
+
end
|
15
|
+
|
16
|
+
def filetype
|
17
|
+
stat("%F")
|
18
|
+
end
|
19
|
+
|
20
|
+
def owner
|
21
|
+
stat("%U")
|
22
|
+
end
|
23
|
+
|
24
|
+
def group
|
25
|
+
stat("%G")
|
26
|
+
end
|
27
|
+
|
28
|
+
def mode
|
29
|
+
stat("%a")
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def stat(format)
|
34
|
+
`chroot #{@rootfs} stat -c #{format} #{@path}`.rstrip
|
35
|
+
end
|
36
|
+
|
37
|
+
def test(op)
|
38
|
+
system("chroot #{@rootfs} test #{op} #{@path}")
|
39
|
+
$? == 0 ? true : false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/toft/node.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'ping'
|
4
|
+
require 'toft/file_checker'
|
5
|
+
|
6
|
+
module Toft
|
7
|
+
class Node
|
8
|
+
PRIVATE_KEY = <<-EOF
|
9
|
+
-----BEGIN RSA PRIVATE KEY-----
|
10
|
+
MIIEpAIBAAKCAQEAwoLge1y9wEcy2WC7CDGXuFFDt+9Zvh+QulfDIbZkpYF7YBw6
|
11
|
+
O3mYTUpucwnqMhnd9jini8bsQghJF3wwxdWmtmurcHAEhN6ZljXZwUu2rojh+D+4
|
12
|
+
PnkOAMPb+w3REmyFYBxfzQ4gBBRXZgKWDTN6Al9hYRFTVSsZJCKJFK+GsWBSc5ie
|
13
|
+
l6IuUnfCbTwvORWVV6g7nGQ5x0JTnApG0qNFDprFkBsLbvHlB6A3lBtHQfJ7W/cZ
|
14
|
+
QXi3LXawD4bhWAl/CHxZHXGpJM7+tREz2yhOoPcHUwwv0xHqe/wBxmJmq84kZ8Co
|
15
|
+
lTi9Y5xNXKDiS6svMDgt3ShpkWcvQ+LE3PhUVQIDAQABAoIBAQCEvfZemeLw9mXw
|
16
|
+
TYA2TknhQqw5OYIAKuCFGuGS/ztea6f75iejcQ8MKDCKF4kZGegNYYqN7HpNcgQX
|
17
|
+
n+xVBsJYGdCM0hVza8pa5XMu4/HO2KGF3k5pbAmvYfqdMUeuEBtRhOuoL+yPfCZM
|
18
|
+
+pTWe3vXZKo7KSy6ocftjhgI4uTD5OtUHZe+Q61K8Ng5723kk4KcbZo4LHEvj4C2
|
19
|
+
nnplt9uxiS04qTPuJohxiwE1pbSybaH5Kndfvzmh1A1q/HKCZNhQKK/jhgvDBtBu
|
20
|
+
hiDfPtKdEeSTbFm11ckJBE1HPdAXoppxwDHQzcq82+vNJdB2P2TsMPklPRb6FuDa
|
21
|
+
dCQ0B5IBAoGBAPUlki+Q6yC4snhJ7r0ipIiMimB+Q6UlrE6ayyc3s/akjvTaqs2z
|
22
|
+
tcZTPUVVLjs0WIRIYwOWMcNmMZoi2s8nGsDiljmd1+smWHPK8A9X8pSUH6Z5k7sS
|
23
|
+
fzno3ytRXohyR2UB6iuUoT3F0VEnaaDjLlNk77DYusAmMsrm8W5PkMHVAoGBAMsf
|
24
|
+
aocc8yrwC7Wa0xfeNreK4F3OayqP6xAu0aSXqj1gtJ6HTTgY2g4jqtnV40NVXwHK
|
25
|
+
7zA4ie5IfmQ/672Te+9ifeWYNqv8faHDJwZFbZ2LwPW/lm5i4Ezn9KMmpLTz+6yj
|
26
|
+
f1bS+Y0NYyxfs1nn7Lx/kvuJVdIV+Ktma+ZHGAiBAoGAHo6NV0KAHHcJP/cvPAIa
|
27
|
+
ci7afMagVfCJNs8SrZPC6eZ/L0QmcDeLW+o6Q+8nMRgIRIzlUqghEdMmMalQjuu3
|
28
|
+
6P0Vbp8fL996vQw5uh/jS+Pewhh7cqEOnMBLORIOb4GXJp8DemUvaAzFV5FLGFPZ
|
29
|
+
DWoSen+5X4QjZqk8xNxEFfUCgYB+xR6xMMo69BH6x6QTc2Zm6s/Y++k6aRPlx7Kj
|
30
|
+
rNxc7iwL/Jme9zOwO2Z4CduKvI9NCSB60e6Tvr7TRmmUqaVh4/B7CKKmeDDYcnm6
|
31
|
+
mj4tY3mMZoQ2ZJNkrCesY4PMQ7HBL1FcGNQSylYo7Zl79Rl1E5HiVvYu5fOK1aNl
|
32
|
+
1t0TAQKBgQCaN91JXWJFXc1rJsDy365SKrX00Nt8rZolFQy2UhhERoCxD0uwbOYm
|
33
|
+
sfWYRr79L2BZkyRHmuxCD24SvHibDev+T0Jh6leXjnrwd/dj3P0tH8+ctET5SXsD
|
34
|
+
CQWv13UgQjiHgQILXSb7xdzpWK1wpDoqIEWQugRyPQDeZhPWVbB4Lg==
|
35
|
+
-----END RSA PRIVATE KEY-----
|
36
|
+
EOF
|
37
|
+
|
38
|
+
include Observable
|
39
|
+
|
40
|
+
def initialize(hostname, ip)
|
41
|
+
@hostname = hostname
|
42
|
+
@ip = ip
|
43
|
+
unless exists?
|
44
|
+
conf_file = generate_lxc_config
|
45
|
+
system "lxc-create -n #{hostname} -f #{conf_file} -t lucid-chef"
|
46
|
+
end
|
47
|
+
@chef_runner = Toft::Chef::ChefRunner.new("#{rootfs}") do |chef_command|
|
48
|
+
run_ssh chef_command
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def exists?
|
53
|
+
`lxc-ls` =~ /#{@hostname}/
|
54
|
+
end
|
55
|
+
|
56
|
+
def start
|
57
|
+
`lxc-start -n #{@hostname} -d`
|
58
|
+
`lxc-wait -n #{@hostname} -s RUNNING`
|
59
|
+
wait_ssh_ready
|
60
|
+
end
|
61
|
+
|
62
|
+
def stop
|
63
|
+
`lxc-stop -n #{@hostname}`
|
64
|
+
`lxc-wait -n #{@hostname} -s STOPPED`
|
65
|
+
end
|
66
|
+
|
67
|
+
def destroy
|
68
|
+
stop
|
69
|
+
`lxc-destroy -n #{@hostname}`
|
70
|
+
changed
|
71
|
+
notify_observers(@hostname)
|
72
|
+
end
|
73
|
+
|
74
|
+
def running?
|
75
|
+
`lxc-info -n #{@hostname}` =~ /RUNNING/
|
76
|
+
end
|
77
|
+
|
78
|
+
def run_ssh(command)
|
79
|
+
raise ArgumentError, "Trying to run empty command on node #{@hostname}", caller if command.blank?
|
80
|
+
output = ""
|
81
|
+
Net::SSH.start(@ip, "root", :key_data => [PRIVATE_KEY]) do |ssh|
|
82
|
+
ssh.exec! command do |ch, stream, data|
|
83
|
+
if stream == :stderr
|
84
|
+
raise RuntimeError, data, caller
|
85
|
+
else
|
86
|
+
output += data
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
puts output
|
91
|
+
yield output if block_given?
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
|
95
|
+
def rm(dir)
|
96
|
+
raise ArgumentError, "Illegal dir path: [#{dir}]", caller if dir.blank? || dir[0] != ?/
|
97
|
+
system "rm -rf #{rootfs}#{dir}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def run_chef(run_list, chef_attributes = nil)
|
101
|
+
@chef_runner.run run_list, chef_attributes
|
102
|
+
end
|
103
|
+
|
104
|
+
def file(path)
|
105
|
+
FileChecker.new(rootfs, path)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def rootfs
|
110
|
+
"/var/lib/lxc/#{@hostname}/rootfs"
|
111
|
+
end
|
112
|
+
|
113
|
+
def wait_ssh_ready
|
114
|
+
while true
|
115
|
+
netstat = `lxc-netstat --name #{@hostname} -ta`
|
116
|
+
break if netstat =~ /ssh/
|
117
|
+
end
|
118
|
+
while true
|
119
|
+
break if Ping.pingecho @ip, 0.1
|
120
|
+
sleep 0.5
|
121
|
+
end
|
122
|
+
puts "SSH connection on #{@ip} is ready..."
|
123
|
+
end
|
124
|
+
|
125
|
+
def generate_lxc_config
|
126
|
+
conf = <<-EOF
|
127
|
+
lxc.network.type = veth
|
128
|
+
lxc.network.flags = up
|
129
|
+
lxc.network.link = br0
|
130
|
+
lxc.network.name = eth0
|
131
|
+
lxc.network.ipv4 = #{@ip}/24
|
132
|
+
EOF
|
133
|
+
conf_file = "/tmp/#{@hostname}-conf"
|
134
|
+
File.open(conf_file, 'w') do |f|
|
135
|
+
f.write(conf);
|
136
|
+
end
|
137
|
+
return conf_file
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'toft/node'
|
2
|
+
|
3
|
+
module Toft
|
4
|
+
class NodeController
|
5
|
+
attr_reader :nodes
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@nodes = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_node(hostname, ip)
|
12
|
+
node = Node.new(hostname, ip)
|
13
|
+
node.add_observer self
|
14
|
+
@nodes[hostname] = node
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(hostname)
|
18
|
+
@nodes.delete hostname
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy_node(hostname)
|
22
|
+
@nodes[hostname].destroy
|
23
|
+
end
|
24
|
+
|
25
|
+
@@instance = NodeController.new
|
26
|
+
class << self
|
27
|
+
def instance
|
28
|
+
@@instance
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/toft/version.rb
ADDED