toft 0.0.3 → 0.0.4

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