toft 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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