toft 0.0.2
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.
- 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