toft 0.0.3 → 0.0.4

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.
Files changed (39) hide show
  1. data/Gemfile.lock +6 -1
  2. data/Rakefile +93 -4
  3. data/features/checker.feature +2 -2
  4. data/features/chef.feature +23 -5
  5. data/features/command.feature +4 -3
  6. data/features/node.feature +21 -6
  7. data/features/step_definitions/chef.rb +11 -3
  8. data/features/step_definitions/command.rb +1 -3
  9. data/features/step_definitions/node.rb +19 -3
  10. data/features/support/env.rb +6 -3
  11. data/fixtures/chef/attributes.json +9 -0
  12. data/lib/toft/chef/chef_attributes.rb +2 -2
  13. data/lib/toft/chef/chef_runner.rb +17 -4
  14. data/lib/toft/node.rb +64 -20
  15. data/lib/toft/node_controller.rb +2 -2
  16. data/lib/toft/version.rb +1 -1
  17. data/lib/toft.rb +5 -2
  18. data/scripts/bin/centos/lxc-prepare-host +175 -0
  19. data/scripts/{ubuntu/bin → bin/share}/install-chef-ubuntu.sh +0 -0
  20. data/scripts/{ubuntu/bin → bin/share}/install-rvm.sh +0 -0
  21. data/scripts/{ubuntu/bin → bin/share}/lxc-create-centos-image +6 -5
  22. data/scripts/{ubuntu/bin → bin/ubuntu}/lxc-create-ubuntu-image +3 -3
  23. data/scripts/bin/ubuntu/lxc-prepare-host +190 -0
  24. data/scripts/cookbooks/lxc/recipes/default.rb +8 -14
  25. data/scripts/{ubuntu/lxc-templates → cookbooks/lxc/templates/default}/lxc-centos-6 +7 -3
  26. data/scripts/cookbooks/lxc/templates/default/{lxc-lucid-chef → lxc-lucid} +2 -2
  27. data/scripts/cookbooks/lxc/templates/default/{lxc-natty-chef → lxc-natty} +2 -2
  28. data/scripts/lxc-templates/files/rc.local +38 -0
  29. data/scripts/lxc-templates/lxc-centos-6 +279 -0
  30. data/scripts/{ubuntu/lxc-templates → lxc-templates}/lxc-lucid +3 -12
  31. data/scripts/{ubuntu/lxc-templates → lxc-templates}/lxc-natty +51 -61
  32. data/spec/fixtures/illegal_syntax.json +1 -0
  33. data/spec/spec_helper.rb +3 -1
  34. data/spec/tuft/chef_attributes_spec.rb +6 -0
  35. data/spec/tuft/chef_runner_spec.rb +34 -0
  36. metadata +55 -21
  37. data/scripts/centos/bin/lxc-prepare-host +0 -39
  38. data/scripts/cookbooks/lxc/files/default/lxc-create-ubuntu-image +0 -75
  39. data/scripts/ubuntu/bin/lxc-prepare-host +0 -24
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- toft (0.0.2)
4
+ toft (0.0.3)
5
5
  net-ssh
6
6
 
7
7
  GEM
@@ -18,6 +18,8 @@ GEM
18
18
  diff-lcs (1.1.3)
19
19
  erubis (2.7.0)
20
20
  ffi (1.0.9)
21
+ fpm (0.3.10)
22
+ json
21
23
  gherkin (2.5.1)
22
24
  json (>= 1.4.6)
23
25
  i18n (0.6.0)
@@ -25,6 +27,7 @@ GEM
25
27
  net-scp (1.0.4)
26
28
  net-ssh (>= 1.99.1)
27
29
  net-ssh (2.1.4)
30
+ rake (0.9.2)
28
31
  rspec (2.6.0)
29
32
  rspec-core (~> 2.6.0)
30
33
  rspec-expectations (~> 2.6.0)
@@ -52,6 +55,8 @@ PLATFORMS
52
55
 
53
56
  DEPENDENCIES
54
57
  cucumber
58
+ fpm
59
+ rake
55
60
  rspec
56
61
  toft!
57
62
  vagrant (>= 0.8.7)
data/Rakefile CHANGED
@@ -1,14 +1,103 @@
1
1
  require "bundler/gem_tasks"
2
2
 
3
+ PROJECT_ROOT = File.dirname(__FILE__)
4
+ LXC_PACKAGE_NAME = "toft-lxc"
5
+
3
6
  desc "clean artifacts"
4
7
  task :clean do
5
8
  `rm -rf pkg`
9
+ `rm -rf tmp`
6
10
  end
7
11
 
8
12
  desc "build gem and scripts package"
9
- task :package => [:build, :package_scripts]
13
+ task :package => [:build, :package_deb, :package_rpm]
14
+
15
+ task :package_deb do
16
+ src_dir = "#{PROJECT_ROOT}/scripts"
17
+ content_dir = "#{PROJECT_ROOT}/pkg/#{LXC_PACKAGE_NAME}"
18
+ mkdir_p content_dir
19
+ mkdir_p "#{content_dir}/usr/local/bin"
20
+ mkdir_p "#{content_dir}/var/cache/lxc"
21
+ mkdir_p "#{content_dir}/usr/lib/lxc/templates"
22
+ cp_r Dir.glob("#{src_dir}/bin/share/*"), "#{content_dir}/usr/local/bin"
23
+ cp_r Dir.glob("#{src_dir}/bin/ubuntu/*"), "#{content_dir}/usr/local/bin"
24
+ cp_r Dir.glob("#{src_dir}/lxc-templates/*"), "#{content_dir}/usr/lib/lxc/templates"
25
+
26
+ post_install_script = <<-eos
27
+ #!/bin/sh -e
28
+ /usr/local/bin/lxc-prepare-host
29
+ eos
30
+ File.open("#{PROJECT_ROOT}/pkg/toft-lxc-post-install.sh", 'w') { |f| f.write(post_install_script) }
31
+
32
+ Dir.chdir("pkg") do
33
+ system <<-EOF
34
+ fpm -s dir \
35
+ -t deb \
36
+ -C #{content_dir} \
37
+ -a all \
38
+ -n #{LXC_PACKAGE_NAME} \
39
+ -v #{Toft::VERSION} \
40
+ -m "Huang Liang<exceedhl@gmail.com>" \
41
+ --description "lxc templates and helper provided by toft" \
42
+ -d rpm \
43
+ -d dnsutils \
44
+ -d lxc \
45
+ -d bridge-utils \
46
+ -d debootstrap \
47
+ -d dhcp3-server \
48
+ -d bind9 \
49
+ -d ntp \
50
+ --replaces lxc \
51
+ --conflicts apparmor \
52
+ --conflicts apparmor-utils \
53
+ --post-install "#{PROJECT_ROOT}/pkg/toft-lxc-post-install.sh" \
54
+ .
55
+ EOF
56
+ end
57
+ end
58
+
59
+ task :package_rpm do
60
+ src_dir = "#{PROJECT_ROOT}/scripts"
61
+ content_dir = "#{PROJECT_ROOT}/pkg/#{LXC_PACKAGE_NAME}"
62
+ mkdir_p content_dir
63
+ mkdir_p "#{content_dir}/usr/local/bin"
64
+ mkdir_p "#{content_dir}/usr/var/lib/lxc"
65
+ mkdir_p "#{content_dir}/var/cache/lxc"
66
+ mkdir_p "#{content_dir}/usr/lib/lxc/templates"
67
+ cp_r Dir.glob("#{src_dir}/bin/share/*"), "#{content_dir}/usr/local/bin"
68
+ cp_r Dir.glob("#{src_dir}/bin/centos/*"), "#{content_dir}/usr/local/bin"
69
+ cp_r Dir.glob("#{src_dir}/lxc-templates/*"), "#{content_dir}/usr/lib/lxc/templates"
70
+
71
+ post_install_script = <<-eos
72
+ #!/bin/sh -e
73
+ /usr/local/bin/lxc-prepare-host
74
+ eos
75
+ File.open("#{PROJECT_ROOT}/pkg/toft-lxc-post-install.sh", 'w') { |f| f.write(post_install_script) }
76
+
77
+ Dir.chdir("pkg") do
78
+ system <<-EOF
79
+ fpm -s dir \
80
+ -t rpm \
81
+ -C #{content_dir} \
82
+ -a all \
83
+ -n #{LXC_PACKAGE_NAME} \
84
+ -v #{Toft::VERSION} \
85
+ -m "Huang Liang<exceedhl@gmail.com>" \
86
+ --description "lxc templates and helper provided by toft" \
87
+ -d bind-utils \
88
+ -d bridge-utils \
89
+ -d dhcp \
90
+ -d bind \
91
+ -d ntp \
92
+ -d libcap-devel \
93
+ --post-install "#{PROJECT_ROOT}/pkg/toft-lxc-post-install.sh" \
94
+ .
95
+ EOF
96
+ end
97
+ end
10
98
 
11
- task :package_scripts do
12
- mkdir_p "pkg"
13
- `tar zcf pkg/toft_ubuntu_scripts-#{Toft::VERSION}.tar.gz scripts/ubuntu`
99
+ desc "run all tests and features"
100
+ task :test do
101
+ `rspec spec`
102
+ `sudo cucumber features`
14
103
  end
@@ -1,13 +1,13 @@
1
1
  Feature: Checkers provided by Toft to help you verify system state
2
2
 
3
3
  Scenario: Dir checker
4
- Given I have a clean running node "n1" with ip "192.168.20.2"
4
+ Given I have a clean running node n1
5
5
  Then Node "n1" should have "directory" "/tmp" owned by user "root" and group "root" with permission "1777"
6
6
  Then Node "n1" should have not file or directory "/non-exist-dir"
7
7
  Then Node "n1" should have file or directory "tmp"
8
8
 
9
9
  Scenario: File checker
10
- Given I have a clean running node "n1" with ip "192.168.20.2"
10
+ Given I have a clean running node n1
11
11
  When Running ssh command "if getent passwd n1; then userdel -fr n1; fi; useradd -m n1" on "n1" should succeed
12
12
  And Running ssh command "sudo -u n1 touch /tmp/a" on "n1" should succeed
13
13
  Then Node "n1" should have file or directory "/tmp/a"
@@ -1,12 +1,12 @@
1
1
  Feature: Chef support
2
2
 
3
3
  Scenario: Run chef recipe on nodes
4
- Given I have a clean running node "n1" with ip "192.168.20.2"
4
+ Given I have a clean running node n1
5
5
  When I run "recipe[test]" on node "n1"
6
6
  Then Node "n1" should have file or directory "/tmp/stub/dir"
7
7
 
8
8
  Scenario: Run chef recipe with attributes
9
- Given I have a clean running node "n1" with ip "192.168.20.2"
9
+ Given I have a clean running node n1
10
10
  When I run "recipe[test::attribute]" on node "n1" and overwrite attributes with:
11
11
  |key|value|
12
12
  |one|one|
@@ -19,7 +19,7 @@ Scenario: Run chef recipe with attributes
19
19
  Then Node "n1" should have file or directory "/tmp/stub/three"
20
20
 
21
21
  Scenario: Run multiple chef recipes
22
- Given I have a clean running node "n1" with ip "192.168.20.2"
22
+ Given I have a clean running node n1
23
23
  When I run recipes on node "n1":
24
24
  |recipe|
25
25
  |recipe[test::role]|
@@ -28,16 +28,34 @@ Scenario: Run multiple chef recipes
28
28
  Then Node "n1" should have file or directory "/tmp/stub/role"
29
29
 
30
30
  Scenario: Run chef role
31
- Given I have a clean running node "n1" with ip "192.168.20.2"
31
+ Given I have a clean running node n1
32
32
  When I run "role[test]" on node "n1"
33
33
  Then Node "n1" should have file or directory "/tmp/stub/role"
34
34
 
35
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"
36
+ Given I have a clean running node n1
37
37
  When I set the role path to empty
38
38
  Then Running chef "recipe[test]" on node "n1" should succeed
39
39
  When I set the cookbook path to empty
40
40
  Then Running chef "recipe[test]" on node "n1" should fail
41
+
42
+ Scenario: Run chef recipe with json attributes file
43
+ Given I have a clean running node n1
44
+ When I run "recipe[test::attribute]" on node "n1" and overwrite attributes with json file "attributes.json"
45
+ Then Node "n1" should have file or directory "/tmp/stub/one"
46
+ Then Node "n1" should have file or directory "/tmp/stub/two_one"
47
+ Then Node "n1" should have file or directory "/tmp/stub/two_two"
48
+ Then Node "n1" should have file or directory "/tmp/stub/three"
49
+
50
+ Scenario: Attributes table should override attributes in json file
51
+ Given I have a clean running node n1
52
+ When I run "recipe[test::attribute]" on node "n1" and overwrite attributes with json file "attributes.json" and chef attributes:
53
+ |key|value|
54
+ |one|override|
55
+ Then Node "n1" should have file or directory "/tmp/stub/override"
56
+ Then Node "n1" should have file or directory "/tmp/stub/two_one"
57
+ Then Node "n1" should have file or directory "/tmp/stub/two_two"
58
+ Then Node "n1" should have file or directory "/tmp/stub/three"
41
59
 
42
60
  Scenario: Run non-exist recipe
43
61
 
@@ -1,19 +1,20 @@
1
1
  Feature: Run ssh command on node
2
2
 
3
3
  Scenario: Run command on node successfully
4
- Given I have a clean running node "n1" with ip "192.168.20.2"
4
+ Given I have a clean running node n1
5
5
  Then Running ssh command "" on "n1" should fail
6
6
  And Running ssh command "ps" on "n1" should succeed
7
7
  And Running ssh command "non-exist-command" on "n1" should fail
8
8
  And Running ssh command "netstat -nr" on "n1" should succeed
9
9
 
10
10
  Scenario: Test rm
11
- Given I have a clean running node "n1" with ip "192.168.20.2"
11
+ Given I have a clean running node n1
12
12
  Then Rm "" on "n1" should fail
13
13
  And Rm "tmp/*" on "n1" should fail
14
14
  And Rm "/tmp/*" on "n1" should succeed
15
15
 
16
16
  Scenario: Check ssh command result
17
- Given I have a clean running node "n1" with ip "192.168.20.2"
17
+ Given I have a clean running node n1
18
18
  Then the result of running ssh command "ps" on "n1" should contain "sshd"
19
+ Then the result of running ssh command "chef-solo" on "n1" should contain "No cookbook found"
19
20
 
@@ -1,21 +1,36 @@
1
1
  Feature: Node management
2
2
 
3
3
  Scenario: Start or stop node
4
- Given I have a clean running node "n1" with ip "192.168.20.2"
4
+ Given I have a clean running node n1
5
5
  Then the node "n1" should be running
6
6
  When I stop node "n1"
7
7
  Then the node "n1" should be stopped
8
8
  When I start node "n1"
9
9
  Then the node "n1" should be running
10
+
11
+ Scenario: Add and remove cnames for a node
12
+ Given I have a clean running node n1
13
+ And I add another node "n2"
14
+ When I add cname "cn1" to "n1"
15
+ Then Running ssh command "ping -c 1 cn1" on "n2" should succeed
16
+ When I remove cname "cn1" from "n1"
17
+ Then Running ssh command "ping -c 1 cn1" on "n2" should fail
18
+ And Node "n2" is destroyed
10
19
 
20
+ Scenario: Create node only by name and fetch their info
21
+ Given I have a clean running node n1
22
+ When I add another node "n3"
23
+ Then Running ssh command "ping -c 1 n1" on "n3" should succeed
24
+ And Running ssh command "ping -c 1 n3" on "n1" should succeed
25
+ And Node "n1" should have ip address same with that obtained from inside it through ssh
26
+ And Node "n3" should have ip address same with that obtained from inside it through ssh
27
+ And Node "n3" is destroyed
28
+
11
29
  Scenario: Create or destroy node
12
- Given I have a clean running node "n1" with ip "192.168.20.2"
30
+ Given I have a clean running node n1
13
31
  Then There should be 1 nodes in the environment
14
32
  When I add another node "n2" with ip "192.168.20.3"
15
33
  Then There should be 2 nodes in the environment
16
34
  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
35
+ Then There should be 1 nodes in the environment
20
36
 
21
-
@@ -7,11 +7,11 @@ When /^I run recipes on node "([^"]*)":$/ do |node, recipes_table|
7
7
  recipes_table.hashes.each do |row|
8
8
  recipes << row[:recipe]
9
9
  end
10
- find(node).run_chef recipes
10
+ find(node).run_chef recipes
11
11
  end
12
12
 
13
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)
14
+ find(node).run_chef run_list, {:attributes => Toft::ChefAttributes.new(table)}
15
15
  end
16
16
 
17
17
  When /^I set the role path to empty$/ do
@@ -32,4 +32,12 @@ end
32
32
 
33
33
  Then /^Running chef "([^"]*)" on node "([^"]*)" should fail$/ do |run_list, node|
34
34
  lambda { find(node).run_chef(run_list) }.should raise_error
35
- end
35
+ end
36
+
37
+ When /^I run "([^"]*)" on node "([^"]*)" and overwrite attributes with json file "([^"]*)"$/ do |run_list, node, json_file|
38
+ find(node).run_chef run_list, :json => CHEF_FIXTURE_PATH + '/attributes.json'
39
+ end
40
+
41
+ When /^I run "([^"]*)" on node "([^"]*)" and overwrite attributes with json file "([^"]*)" and chef attributes:$/ do |run_list, node, json_file, table|
42
+ find(node).run_chef run_list, {:json => CHEF_FIXTURE_PATH + '/attributes.json', :attributes => Toft::ChefAttributes.new(table)}
43
+ end
@@ -15,7 +15,5 @@ Then /^Rm "([^"]*)" on "([^"]*)" should succeed$/ do |dir, node|
15
15
  end
16
16
 
17
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
18
+ find(node).get_ssh_result(cmd).should include(s)
21
19
  end
@@ -1,11 +1,15 @@
1
- Given /^I have a clean running node "([^"]*)" with ip "([^"]*)"$/ do |node, ip|
2
- create_node node, ip, "centos-6"
1
+ Given /^I have a clean running node n1$/ do
3
2
  @n1.start
4
3
  @n1.rm "/tmp/stub"
5
4
  end
6
5
 
7
6
  When /^I add another node "([^"]*)" with ip "([^"]*)"$/ do |node, ip|
8
- create_node node, ip, "centos-6"
7
+ create_node node, {:ip => ip, :type => CONTAINER_TYPE}
8
+ end
9
+
10
+ When /^I add another node "([^"]*)"$/ do |node|
11
+ n = create_node node, {:type => CONTAINER_TYPE}
12
+ n.start
9
13
  end
10
14
 
11
15
  When /^I destroy node "([^"]*)"$/ do |node|
@@ -36,3 +40,15 @@ Then /^the node "([^"]*)" should be running$/ do |node|
36
40
  find(node).should be_running
37
41
  end
38
42
 
43
+ Then /^Node "([^"]*)" should have ip address same with that obtained from inside it through ssh$/ do |node|
44
+ n = find(node)
45
+ n.get_ssh_result("ifconfig eth0 | grep 'inet addr:'").should include(n.ip)
46
+ end
47
+
48
+ When /^I add cname "([^"]*)" to "([^"]*)"$/ do |cname, node|
49
+ find(node).add_cname cname
50
+ end
51
+
52
+ When /^I remove cname "([^"]*)" from "([^"]*)"$/ do |cname, node|
53
+ find(node).remove_cname cname
54
+ end
@@ -2,15 +2,18 @@ $:.unshift(File.dirname(__FILE__) + '/../../lib')
2
2
  require 'rspec/expectations'
3
3
  require 'toft'
4
4
 
5
- Toft.cookbook_path = File.dirname(__FILE__) + '/../../fixtures/chef/cookbooks'
6
- Toft.role_path = File.dirname(__FILE__) + '/../../fixtures/chef/roles'
5
+ CHEF_FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/chef'
6
+ CONTAINER_TYPE = "centos-6"
7
7
 
8
8
  World(Toft)
9
9
 
10
10
  include Toft
11
- n1 = create_node "n1", "192.168.20.2", "centos-6"
11
+ n1 = create_node "n1", {:ip => "192.168.20.2", :type => CONTAINER_TYPE}
12
12
 
13
13
  Before do
14
+ Toft.cookbook_path = CHEF_FIXTURE_PATH + '/cookbooks'
15
+ Toft.role_path = CHEF_FIXTURE_PATH + '/roles'
16
+
14
17
  @n1 = n1
15
18
  end
16
19
 
@@ -0,0 +1,9 @@
1
+ {
2
+ "one" : "one",
3
+ "two" :
4
+ {
5
+ "one" : "two_one",
6
+ "two" : "two_two"
7
+ },
8
+ "three" : "three"
9
+ }
@@ -4,11 +4,11 @@ module Toft
4
4
  class ChefAttributes
5
5
  attr_reader :attributes
6
6
 
7
- def initialize(cuke_ast_table)
7
+ def initialize(cuke_ast_table = nil)
8
8
  @attributes = {}
9
9
  cuke_ast_table.hashes.each do |row|
10
10
  add_attribute row[:key], row[:value]
11
- end
11
+ end unless cuke_ast_table.nil?
12
12
  end
13
13
 
14
14
  def add_attribute(key, value)
@@ -17,15 +17,28 @@ module Toft
17
17
  @command_runner = command_runner
18
18
  end
19
19
 
20
- def run(run_list, chef_attributes)
20
+ def run(run_list, params = {})
21
+ override_attributes_hash = parse_attributes params[:json], params[:attributes]
21
22
  copy_chef_material
22
23
  generate_solo_rb
23
- generate_json ([] << run_list).flatten, chef_attributes
24
+ generate_json ([] << run_list).flatten, override_attributes_hash
24
25
  @command_runner.call "chef-solo -c #{DEST_CHEF_SOLO_PATH} -j #{DEST_CHEF_JSON_PATH}"
25
26
  end
26
27
 
27
28
  private
29
+ def parse_attributes(json_file_path, chef_attributes)
30
+ chef_attributes = chef_attributes || ChefAttributes.new
31
+ chef_attributes_from_json ||= {}
32
+ chef_attributes_from_json = read_json_file json_file_path unless json_file_path.blank?
33
+ chef_attributes_from_json.merge chef_attributes.attributes
34
+ end
35
+
36
+ def read_json_file(json_file_path)
37
+ JSON.parse(File.read(json_file_path))
38
+ end
39
+
28
40
  def copy_chef_material
41
+ raise ArgumentError, "Toft.cookbook_path can not be empty!" if Toft.cookbook_path.blank?
29
42
  rm_rf "#{@root_dir}#{DEST_CHEF_TMP}"
30
43
  mkdir_p "#{@root_dir}#{DEST_CHEF_TMP}"
31
44
  cp_r Toft.cookbook_path, "#{@root_dir}#{DEST_COOKBOOK_PATH}"
@@ -48,9 +61,9 @@ cookbook_path ["#{DEST_COOKBOOK_PATH}"]
48
61
  end
49
62
  end
50
63
 
51
- def generate_json(run_list, chef_attributes)
64
+ def generate_json(run_list, override_attributes_hash)
52
65
  run_list = {"run_list" => run_list}
53
- run_list.merge!(chef_attributes.attributes) unless chef_attributes.nil?
66
+ run_list.merge!(override_attributes_hash)
54
67
  File.open("#{@root_dir}#{DEST_CHEF_JSON_PATH}", 'w') do |f|
55
68
  f.write(run_list.to_json);
56
69
  end
data/lib/toft/node.rb CHANGED
@@ -35,14 +35,19 @@ CQWv13UgQjiHgQILXSb7xdzpWK1wpDoqIEWQugRyPQDeZhPWVbB4Lg==
35
35
  -----END RSA PRIVATE KEY-----
36
36
  EOF
37
37
 
38
+ TIMOUT_IN_SECONDS = 60
39
+ TRY_INTERVAL = 0.5
40
+
38
41
  include Observable
39
42
 
40
- def initialize(hostname, ip, type)
43
+ def initialize(hostname, options)
44
+ options = {:ip => DYNAMIC_IP, :netmask => "24", :type => "natty"}.merge(options)
41
45
  @hostname = hostname
42
- @ip = ip
46
+ @ip = options[:ip]
47
+ @netmask = options[:netmask]
43
48
  unless exists?
44
49
  conf_file = generate_lxc_config
45
- system "lxc-create -n #{hostname} -f #{conf_file} -t #{type.to_s}"
50
+ system "lxc-create -n #{hostname} -f #{conf_file} -t #{options[:type].to_s}"
46
51
  end
47
52
  @chef_runner = Toft::Chef::ChefRunner.new("#{rootfs}") do |chef_command|
48
53
  run_ssh chef_command
@@ -74,62 +79,97 @@ CQWv13UgQjiHgQILXSb7xdzpWK1wpDoqIEWQugRyPQDeZhPWVbB4Lg==
74
79
  def running?
75
80
  `lxc-info -n #{@hostname}` =~ /RUNNING/
76
81
  end
82
+
83
+ def add_cname(cname)
84
+ run_ssh "echo -e 'update add #{cname}.#{Toft::DOMAIN} 86400 CNAME #{@hostname}.#{Toft::DOMAIN}\\nsend' | nsupdate"
85
+ end
86
+
87
+ def remove_cname(cname)
88
+ run_ssh "echo -e 'update delete #{cname}.#{Toft::DOMAIN}\\nsend' | nsupdate"
89
+ end
77
90
 
78
91
  def run_ssh(command)
79
92
  raise ArgumentError, "Trying to run empty command on node #{@hostname}", caller if command.blank?
80
- output = ""
81
93
  error = false
82
- Net::SSH.start(@ip, "root", :key_data => [PRIVATE_KEY]) do |ssh|
94
+ Net::SSH.start(fqdn, "root", :key_data => [PRIVATE_KEY]) do |ssh|
83
95
  ssh.exec! command do |ch, stream, data|
84
96
  if stream == :stderr
85
97
  error = true
86
98
  end
87
- output += data
99
+ yield data if block_given?
100
+ puts data
88
101
  end
89
102
  end
90
- raise RuntimeError, output, caller if error
91
- puts output
92
- return yield output if block_given?
103
+ raise RuntimeError, "Error happened while executing command [#{command}]!", caller if error
93
104
  return true
94
105
  end
106
+
107
+ def get_ssh_result(cmd)
108
+ output = ""
109
+ run_ssh(cmd) do |data|
110
+ output += data
111
+ end
112
+ return output
113
+ end
95
114
 
96
115
  def rm(dir)
97
116
  raise ArgumentError, "Illegal dir path: [#{dir}]", caller if dir.blank? || dir[0] != ?/
98
117
  system "rm -rf #{rootfs}#{dir}"
99
118
  end
100
119
 
101
- def run_chef(run_list, chef_attributes = nil)
102
- @chef_runner.run run_list, chef_attributes
120
+ def run_chef(run_list, params = {})
121
+ @chef_runner.run run_list, params
103
122
  end
104
123
 
105
124
  def file(path)
106
125
  FileChecker.new(rootfs, path)
107
126
  end
127
+
128
+ def ip
129
+ @ip == Toft::DYNAMIC_IP ? `dig +short #{fqdn}`.strip : @ip
130
+ end
108
131
 
109
132
  private
110
133
  def rootfs
111
134
  "/var/lib/lxc/#{@hostname}/rootfs"
112
135
  end
113
-
114
- def wait_ssh_ready
136
+
137
+ def wait_sshd_running
115
138
  while true
116
139
  netstat = `lxc-netstat --name #{@hostname} -ta`
117
- break if netstat =~ /ssh/
140
+ return if netstat =~ /ssh/
118
141
  end
119
- while true
120
- break if Ping.pingecho @ip, 0.1
121
- sleep 0.5
142
+ end
143
+
144
+ def wait_remote_host_reachable
145
+ max_try = TIMOUT_IN_SECONDS / TRY_INTERVAL
146
+ try = 0
147
+ while try < max_try
148
+ begin
149
+ break if Ping.pingecho fqdn, 0.1
150
+ rescue Exception
151
+ # fix the strange pingcho exception
152
+ end
153
+ sleep TRY_INTERVAL
154
+ try += 1
122
155
  end
123
- puts "SSH connection on #{@ip} is ready..."
156
+ raise RuntimeError, "Remote machine not responding." if try >= max_try
157
+ end
158
+
159
+ def wait_ssh_ready
160
+ wait_sshd_running
161
+ wait_remote_host_reachable
162
+ puts "SSH connection on '#{@hostname}/#{@ip}' is ready..."
124
163
  end
125
164
 
126
165
  def generate_lxc_config
166
+ full_ip = @ip == Toft::DYNAMIC_IP ? "#{@ip}" : "#{@ip}/#{@netmask}"
127
167
  conf = <<-EOF
128
168
  lxc.network.type = veth
129
169
  lxc.network.flags = up
130
170
  lxc.network.link = br0
131
171
  lxc.network.name = eth0
132
- lxc.network.ipv4 = #{@ip}/24
172
+ lxc.network.ipv4 = #{full_ip}
133
173
  EOF
134
174
  conf_file = "/tmp/#{@hostname}-conf"
135
175
  File.open(conf_file, 'w') do |f|
@@ -137,5 +177,9 @@ lxc.network.ipv4 = #{@ip}/24
137
177
  end
138
178
  return conf_file
139
179
  end
180
+
181
+ def fqdn
182
+ "#{@hostname}.#{Toft::DOMAIN}"
183
+ end
140
184
  end
141
- end
185
+ end
@@ -8,8 +8,8 @@ module Toft
8
8
  @nodes = {}
9
9
  end
10
10
 
11
- def create_node(hostname, ip, type)
12
- node = Node.new(hostname, ip, type)
11
+ def create_node(hostname, options)
12
+ node = Node.new(hostname, options)
13
13
  node.add_observer self
14
14
  @nodes[hostname] = node
15
15
  end
data/lib/toft/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Toft
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/toft.rb CHANGED
@@ -4,12 +4,15 @@ require 'toft/chef/chef_attributes'
4
4
  require 'toft/chef/chef_runner'
5
5
 
6
6
  module Toft
7
+ DYNAMIC_IP = "0.0.0.0"
8
+ DOMAIN = "foo"
9
+
7
10
  class << self
8
11
  attr_accessor :cookbook_path, :role_path
9
12
  end
10
13
 
11
- def create_node(hostname, ip, type)
12
- NodeController.instance.create_node(hostname, ip, type)
14
+ def create_node(hostname, options = {})
15
+ NodeController.instance.create_node(hostname, options)
13
16
  end
14
17
 
15
18
  def find(hostname)