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 ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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,4 @@
1
+ default[:one] = "test"
2
+ default[:three] = "test"
3
+ default[:two][:one] = "test"
4
+ default[:two][:two] = "test"
@@ -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,4 @@
1
+ directory "/tmp/stub/dir" do
2
+ action :create
3
+ recursive true
4
+ end
@@ -0,0 +1,4 @@
1
+ directory "/tmp/stub/role" do
2
+ action :create
3
+ recursive true
4
+ end
@@ -0,0 +1,3 @@
1
+ name 'test'
2
+ description 'Test!'
3
+ run_list "recipe[test::role]"
@@ -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
@@ -0,0 +1,3 @@
1
+ module Toft
2
+ VERSION = "0.0.2"
3
+ end