auser-poolparty 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/CHANGELOG +8 -0
  2. data/README.txt +10 -10
  3. data/Rakefile +30 -21
  4. data/{web/static/site/images → assets}/clouds.png +0 -0
  5. data/bin/instance +39 -34
  6. data/bin/pool +44 -29
  7. data/bin/poolnotify +34 -0
  8. data/config/haproxy.conf +1 -1
  9. data/config/heartbeat_authkeys.conf +1 -1
  10. data/config/monit/haproxy.monit.conf +2 -1
  11. data/config/nginx.conf +1 -1
  12. data/config/reconfigure_instances_script.sh +28 -9
  13. data/config/sample-config.yml +1 -1
  14. data/lib/core/string.rb +3 -0
  15. data/lib/modules/ec2_wrapper.rb +47 -22
  16. data/lib/modules/file_writer.rb +38 -0
  17. data/lib/modules/sprinkle_overrides.rb +32 -0
  18. data/lib/modules/vlad_override.rb +5 -4
  19. data/lib/poolparty.rb +14 -10
  20. data/lib/poolparty/application.rb +33 -19
  21. data/lib/poolparty/master.rb +227 -105
  22. data/lib/poolparty/optioner.rb +8 -4
  23. data/lib/poolparty/plugin.rb +34 -4
  24. data/lib/poolparty/provider/packages/haproxy.rb +0 -15
  25. data/lib/poolparty/provider/packages/heartbeat.rb +1 -1
  26. data/lib/poolparty/provider/packages/ruby.rb +6 -6
  27. data/lib/poolparty/provider/packages/s3fuse.rb +9 -2
  28. data/lib/poolparty/provider/provider.rb +65 -25
  29. data/lib/poolparty/remote_instance.rb +95 -74
  30. data/lib/poolparty/remoter.rb +48 -37
  31. data/lib/poolparty/remoting.rb +41 -17
  32. data/lib/poolparty/scheduler.rb +4 -4
  33. data/lib/poolparty/tasks.rb +1 -1
  34. data/lib/poolparty/tasks/package.rake +53 -0
  35. data/lib/poolparty/tasks/plugins.rake +1 -1
  36. data/poolparty.gemspec +50 -58
  37. data/spec/application_spec.rb +28 -0
  38. data/spec/core_spec.rb +9 -0
  39. data/spec/ec2_wrapper_spec.rb +87 -0
  40. data/spec/file_writer_spec.rb +73 -0
  41. data/spec/files/describe_response +37 -0
  42. data/spec/files/multi_describe_response +69 -0
  43. data/spec/files/remote_desc_response +37 -0
  44. data/spec/helpers/ec2_mock.rb +3 -0
  45. data/spec/master_spec.rb +302 -78
  46. data/spec/monitors/cpu_monitor_spec.rb +2 -1
  47. data/spec/monitors/memory_spec.rb +1 -0
  48. data/spec/monitors/misc_monitor_spec.rb +1 -0
  49. data/spec/monitors/web_spec.rb +1 -0
  50. data/spec/optioner_spec.rb +12 -0
  51. data/spec/plugin_manager_spec.rb +10 -10
  52. data/spec/plugin_spec.rb +6 -3
  53. data/spec/pool_binary_spec.rb +3 -0
  54. data/spec/poolparty_spec.rb +12 -7
  55. data/spec/provider_spec.rb +1 -0
  56. data/spec/remote_instance_spec.rb +18 -18
  57. data/spec/remoter_spec.rb +4 -2
  58. data/spec/remoting_spec.rb +10 -2
  59. data/spec/scheduler_spec.rb +0 -6
  60. data/spec/spec_helper.rb +13 -0
  61. metadata +83 -52
  62. data/Manifest +0 -115
  63. data/lib/poolparty/tmp.rb +0 -46
  64. data/misc/basics_tutorial.txt +0 -142
  65. data/web/static/conf/nginx.conf +0 -22
  66. data/web/static/site/images/balloon.png +0 -0
  67. data/web/static/site/images/cb.png +0 -0
  68. data/web/static/site/images/railsconf_preso_img.png +0 -0
  69. data/web/static/site/index.html +0 -71
  70. data/web/static/site/javascripts/application.js +0 -3
  71. data/web/static/site/javascripts/corner.js +0 -178
  72. data/web/static/site/javascripts/jquery-1.2.6.pack.js +0 -11
  73. data/web/static/site/misc.html +0 -42
  74. data/web/static/site/storage/pool_party_presentation.pdf +0 -0
  75. data/web/static/site/stylesheets/application.css +0 -100
  76. data/web/static/site/stylesheets/reset.css +0 -17
  77. data/web/static/src/layouts/application.haml +0 -25
  78. data/web/static/src/pages/index.haml +0 -25
  79. data/web/static/src/pages/misc.haml +0 -5
  80. data/web/static/src/stylesheets/application.sass +0 -100
data/spec/core_spec.rb CHANGED
@@ -12,4 +12,13 @@ describe "Hash" do
12
12
  a.safe_merge!(b)
13
13
  a.should == {:a => "10", :b => "20", :c => "40"}
14
14
  end
15
+ end
16
+ describe "String" do
17
+ it "should be able to convert a big string with \n to a runnable string" do
18
+ str =<<-EOS
19
+ echo 'hi'
20
+ puts 'hi'
21
+ EOS
22
+ str.runnable.should == "echo 'hi' && puts 'hi'"
23
+ end
15
24
  end
@@ -0,0 +1,87 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require File.dirname(__FILE__) + "/helpers/ec2_mock"
3
+
4
+ class EC2Test
5
+ include Ec2Wrapper
6
+
7
+ def get_non_empty_instance_description
8
+ @resp ||= EC2::Response.parse(:xml => read_file("describe_response"))
9
+ end
10
+ def get_non_empty_instances_description
11
+ @resp2 ||= EC2::Response.parse(:xml => read_file("multi_describe_response"))
12
+ end
13
+ def get_remote_inst_desc
14
+ @resp3 = EC2::Response.parse(:xml => read_file("remote_desc_response"))
15
+ end
16
+ def read_file(name)
17
+ open("#{File.dirname(__FILE__)}/files/#{name}").read
18
+ end
19
+ end
20
+ describe "EC2ResponseObject" do
21
+ before(:each) do
22
+ @test = EC2Test.new
23
+ @r = @test.get_non_empty_instance_description
24
+ @rs = @test.get_non_empty_instances_description
25
+ @rst = @test.get_remote_inst_desc
26
+ end
27
+ describe "single instance" do
28
+ it "should be able to get the response object from the query" do
29
+ EC2ResponseObject.get_response_from(@r).should_not be_nil
30
+ end
31
+ it "should return an EC2:Response from EC2ResponseObject.get_response_from" do
32
+ EC2ResponseObject.get_response_from(@r).class.should == EC2::Response
33
+ end
34
+ it "should be able to grab the keypair name from the response object" do
35
+ EC2ResponseObject.get_response_from(@r).instancesSet.item.instanceId.should == "i-60bc6a09"
36
+ end
37
+ it "should be able to list out the running instances" do
38
+ EC2ResponseObject.get_descriptions(@r)
39
+ end
40
+ it "should return an array of instances" do
41
+ EC2ResponseObject.get_descriptions(@r).class.should == Array
42
+ end
43
+ it "should be able to iterate through the instances" do
44
+ EC2ResponseObject.get_descriptions(@r).select {|a| a[:keypair] == "auser"}.should_not be_empty
45
+ end
46
+ end
47
+ describe "multiple responses" do
48
+ it "should be able to get the response object from the query" do
49
+ EC2ResponseObject.get_response_from(@r).should_not be_nil
50
+ end
51
+ it "should return an EC2:Response from EC2ResponseObject.get_response_from" do
52
+ EC2ResponseObject.get_response_from(@r).class.should == EC2::Response
53
+ end
54
+ it "should be able to grab the keypair name from the response object" do
55
+ EC2ResponseObject.get_response_from(@r).instancesSet.item.instanceId.should == "i-60bc6a09"
56
+ end
57
+ it "should be able to list out the running instances" do
58
+ EC2ResponseObject.get_descriptions(@r)
59
+ end
60
+ it "should return an array of instances" do
61
+ EC2ResponseObject.get_descriptions(@r).class.should == Array
62
+ end
63
+ it "should be able to iterate through the instances" do
64
+ EC2ResponseObject.get_descriptions(@r).select {|a| a[:keypair] == "auser"}.should_not be_empty
65
+ end
66
+ end
67
+ describe "multiple responses" do
68
+ it "should be able to get the response object from the query" do
69
+ EC2ResponseObject.get_response_from(@rst).should_not be_nil
70
+ end
71
+ it "should return an EC2:Response from EC2ResponseObject.get_response_from" do
72
+ EC2ResponseObject.get_response_from(@rst).class.should == EC2::Response
73
+ end
74
+ it "should be able to grab the keypair name from the response object" do
75
+ EC2ResponseObject.get_response_from(@rst).instancesSet.item.instanceId.should == "i-94f82efd"
76
+ end
77
+ it "should be able to list out the running instances" do
78
+ EC2ResponseObject.get_descriptions(@rst)
79
+ end
80
+ it "should return an array of instances" do
81
+ EC2ResponseObject.get_descriptions(@rst).class.should == Array
82
+ end
83
+ it "should be able to iterate through the instances" do
84
+ EC2ResponseObject.get_descriptions(@rst).select {|a| a[:keypair] == "auser"}.should_not be_empty
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,73 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class TestClass
4
+ include FileWriter
5
+ end
6
+ describe "FileWriter" do
7
+ before(:each) do
8
+ @instance = RemoteInstance.new
9
+ @instance.stub!(:name).and_return "node0"
10
+ @instance.stub!(:ip).and_return "127.0.0.1"
11
+ @test = TestClass.new
12
+ end
13
+ describe "writing to the temp directory with a string" do
14
+ before(:each) do
15
+ @test.write_to_file_for("haproxy", @instance, "topical")
16
+ @outfile = "#{@test.base_tmp_dir}/node0-haproxy"
17
+ end
18
+ after(:each) do
19
+ FileUtils.rm @outfile
20
+ end
21
+ it "should be able to write a file to the tmp directory with a string" do
22
+ File.file?(@outfile).should == true
23
+ end
24
+ it "should be able to write to the file with the string" do
25
+ open(@outfile).read.should == "topical"
26
+ end
27
+ end
28
+
29
+ describe "writing to the temp directory with a block" do
30
+ before(:each) do
31
+ @test.write_to_file_for("haproxy", @instance) do
32
+ "Hello topical"
33
+ end
34
+ @outfile = "#{@test.base_tmp_dir}/node0-haproxy"
35
+ end
36
+ after(:each) do
37
+ FileUtils.rm @outfile
38
+ end
39
+ it "should be able to write a file to the tmp directory with a string" do
40
+ File.file?(@outfile).should == true
41
+ end
42
+ it "should be able to write to the file with the string" do
43
+ open(@outfile).read.should == "Hello topical"
44
+ end
45
+ describe "for output of block" do
46
+ before(:each) do
47
+ @test.write_to_file_for("haproxy", @instance) do
48
+ "Fix and me"
49
+ "Hello topical"
50
+ end
51
+ end
52
+ it "should just write the final output of the block to the file" do
53
+ open(@outfile).read.should == "Hello topical"
54
+ end
55
+ end
56
+ end
57
+
58
+ describe "without a node" do
59
+ before(:each) do
60
+ @test.write_to_file_for("haproxy") do
61
+ "Hello topical"
62
+ end
63
+ @outfile = "#{@test.base_tmp_dir}/haproxy"
64
+ end
65
+ after(:each) do
66
+ FileUtils.rm @outfile
67
+ end
68
+ it "should write to the master file without a node name" do
69
+ File.file?(@outfile).should == true
70
+ end
71
+ end
72
+
73
+ end
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0"?>
2
+ <DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2008-02-01/">
3
+ <reservationSet>
4
+ <item>
5
+ <reservationId>r-6d539104</reservationId>
6
+ <ownerId>161964561164</ownerId>
7
+ <groupSet>
8
+ <item>
9
+ <groupId>default</groupId>
10
+ </item>
11
+ </groupSet>
12
+ <instancesSet>
13
+ <item>
14
+ <instanceId>i-60bc6a09</instanceId>
15
+ <imageId>ami-4bb05422</imageId>
16
+ <instanceState>
17
+ <code>16</code>
18
+ <name>running</name>
19
+ </instanceState>
20
+ <privateDnsName>domU-12-31-39-00-86-15.compute-1.internal</privateDnsName>
21
+ <dnsName>ec2-75-101-242-244.compute-1.amazonaws.com</dnsName>
22
+ <reason/>
23
+ <keyName>auser</keyName>
24
+ <amiLaunchIndex>0</amiLaunchIndex>
25
+ <productCodes/>
26
+ <instanceType>m1.small</instanceType>
27
+ <launchTime>2008-06-25T20:14:35.000Z</launchTime>
28
+ <placement>
29
+ <availabilityZone>us-east-1b</availabilityZone>
30
+ </placement>
31
+ <kernelId>aki-a71cf9ce</kernelId>
32
+ <ramdiskId>ari-a51cf9cc</ramdiskId>
33
+ </item>
34
+ </instancesSet>
35
+ </item>
36
+ </reservationSet>
37
+ </DescribeInstancesResponse>
@@ -0,0 +1,69 @@
1
+ <?xml version="1.0"?>
2
+ <DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2008-02-01/">
3
+ <reservationSet>
4
+ <item>
5
+ <reservationId>r-7678ba1f</reservationId>
6
+ <ownerId>802044448869</ownerId>
7
+ <groupSet>
8
+ <item>
9
+ <groupId>default</groupId>
10
+ </item>
11
+ </groupSet>
12
+ <instancesSet>
13
+ <item>
14
+ <instanceId>i-6df12704</instanceId>
15
+ <imageId>ami-3057b259</imageId>
16
+ <instanceState>
17
+ <code>16</code>
18
+ <name>running</name>
19
+ </instanceState>
20
+ <privateDnsName>ip-10-251-199-97.ec2.internal</privateDnsName>
21
+ <dnsName>ec2-72-44-39-61.compute-1.amazonaws.com</dnsName>
22
+ <reason/>
23
+ <keyName>pjc-keypair</keyName>
24
+ <amiLaunchIndex>0</amiLaunchIndex>
25
+ <productCodes/>
26
+ <instanceType>m1.small</instanceType>
27
+ <launchTime>2008-06-26T21:55:52.000Z</launchTime>
28
+ <placement>
29
+ <availabilityZone>us-east-1c</availabilityZone>
30
+ </placement>
31
+ <kernelId>aki-a71cf9ce</kernelId>
32
+ <ramdiskId>ari-a51cf9cc</ramdiskId>
33
+ </item>
34
+ </instancesSet>
35
+ </item>
36
+ <item>
37
+ <reservationId>r-4978ba20</reservationId>
38
+ <ownerId>802044448869</ownerId>
39
+ <groupSet>
40
+ <item>
41
+ <groupId>default</groupId>
42
+ </item>
43
+ </groupSet>
44
+ <instancesSet>
45
+ <item>
46
+ <instanceId>i-6cf12705</instanceId>
47
+ <imageId>ami-3057b259</imageId>
48
+ <instanceState>
49
+ <code>16</code>
50
+ <name>running</name>
51
+ </instanceState>
52
+ <privateDnsName>ip-10-251-198-230.ec2.internal</privateDnsName>
53
+ <dnsName>ec2-75-101-236-239.compute-1.amazonaws.com</dnsName>
54
+ <reason/>
55
+ <keyName>pjc-keypair</keyName>
56
+ <amiLaunchIndex>0</amiLaunchIndex>
57
+ <productCodes/>
58
+ <instanceType>m1.small</instanceType>
59
+ <launchTime>2008-06-26T21:55:55.000Z</launchTime>
60
+ <placement>
61
+ <availabilityZone>us-east-1c</availabilityZone>
62
+ </placement>
63
+ <kernelId>aki-a71cf9ce</kernelId>
64
+ <ramdiskId>ari-a51cf9cc</ramdiskId>
65
+ </item>
66
+ </instancesSet>
67
+ </item>
68
+ </reservationSet>
69
+ </DescribeInstancesResponse>
@@ -0,0 +1,37 @@
1
+ <?xml version="1.0"?>
2
+ <DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2008-02-01/">
3
+ <reservationSet>
4
+ <item>
5
+ <reservationId>r-ec8d4085</reservationId>
6
+ <ownerId>161964561164</ownerId>
7
+ <groupSet>
8
+ <item>
9
+ <groupId>default</groupId>
10
+ </item>
11
+ </groupSet>
12
+ <instancesSet>
13
+ <item>
14
+ <instanceId>i-94f82efd</instanceId>
15
+ <imageId>ami-4bb05422</imageId>
16
+ <instanceState>
17
+ <code>16</code>
18
+ <name>running</name>
19
+ </instanceState>
20
+ <privateDnsName>ip-10-251-203-148.ec2.internal</privateDnsName>
21
+ <dnsName>ec2-75-101-205-180.compute-1.amazonaws.com</dnsName>
22
+ <reason/>
23
+ <keyName>auser</keyName>
24
+ <amiLaunchIndex>0</amiLaunchIndex>
25
+ <productCodes/>
26
+ <instanceType>m1.small</instanceType>
27
+ <launchTime>2008-06-27T02:25:26.000Z</launchTime>
28
+ <placement>
29
+ <availabilityZone>us-east-1a</availabilityZone>
30
+ </placement>
31
+ <kernelId>aki-a71cf9ce</kernelId>
32
+ <ramdiskId>ari-a51cf9cc</ramdiskId>
33
+ </item>
34
+ </instancesSet>
35
+ </item>
36
+ </reservationSet>
37
+ </DescribeInstancesResponse>
@@ -24,6 +24,9 @@ module PoolParty
24
24
  def get_instances_description
25
25
  instances
26
26
  end
27
+ def get_non_empty_instances_description
28
+ EC2::Response.parse(:xml => open("#{File.dirname(__FILE__)}/../files/response.txt").read)
29
+ end
27
30
  # Fake the ec2 connection
28
31
  def ec2
29
32
  @ec2 ||= EC2::Base.new(:access_key_id => "not a key", :secret_access_key => "not a key")
data/spec/master_spec.rb CHANGED
@@ -2,14 +2,18 @@ require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe "Master" do
4
4
  before(:each) do
5
+ stub_option_load
5
6
  Kernel.stub!(:system).and_return true
6
7
  Kernel.stub!(:exec).and_return true
7
- Kernel.stub!(:sleep).and_return true # WHy wait?
8
+ Kernel.stub!(:sleep).and_return true # WHy wait?, just do it
8
9
 
9
10
  Application.options.stub!(:contract_when).and_return("web > 30.0\n cpu < 0.10")
10
11
  Application.options.stub!(:expand_when).and_return("web < 3.0\n cpu > 0.80")
11
12
  @master = Master.new
12
13
  end
14
+ after(:all) do
15
+ @master.cleanup_tmp_directory(nil)
16
+ end
13
17
  it "should launch the first instances and set the first as the master and the rest as slaves" do
14
18
  Application.stub!(:minimum_instances).and_return(1)
15
19
  Application.stub!(:verbose).and_return(false) # Hide messages
@@ -20,11 +24,11 @@ describe "Master" do
20
24
  @master.stub!(:wait).and_return true
21
25
 
22
26
  @master.should_receive(:launch_new_instance!).and_return(
23
- {:instance_id => "i-5849ba", :ip => "ip-127-0-0-1.aws.amazon.com", :status => "running"})
27
+ {:instance_id => "i-5849ba", :ip => "127.0.0.1", :status => "running"})
24
28
  @master.stub!(:list_of_nonterminated_instances).and_return(
25
- [{:instance_id => "i-5849ba", :ip => "ip-127-0-0-1.aws.amazon.com", :status => "running"}])
29
+ [{:instance_id => "i-5849ba", :ip => "127.0.0.1", :status => "running"}])
26
30
 
27
- node = RemoteInstance.new({:instance_id => "i-5849ba", :ip => "ip-127-0-0-1.aws.amazon.com", :status => "running"})
31
+ node = RemoteInstance.new({:instance_id => "i-5849ba", :ip => "127.0.0.1", :status => "running"})
28
32
  node.stub!(:scp).and_return "true"
29
33
  node.stub!(:ssh).and_return "true"
30
34
 
@@ -34,14 +38,36 @@ describe "Master" do
34
38
 
35
39
  @master.nodes.first.instance_id.should == "i-5849ba"
36
40
  end
41
+ describe "Singleton methods" do
42
+ before(:each) do
43
+ @master = Master.new
44
+ @instance = RemoteInstance.new
45
+ @blk = Proc.new {puts "new"}
46
+ Master.stub!(:new).once.and_return @master
47
+ end
48
+ it "should be able to run with_nodes" do
49
+ Master.should_receive(:new).once.and_return @master
50
+ @master.should_receive(:nodes).once.and_return []
51
+ Master.with_nodes &@blk
52
+ end
53
+ it "should run the block on each node" do
54
+ collection = [@instance]
55
+ @master.should_receive(:nodes).once.and_return collection
56
+ collection.should_receive(:each).once
57
+ Master.with_nodes &@blk
58
+ end
59
+ end
37
60
  describe "with stubbed instances" do
38
61
  before(:each) do
39
62
  @master.stub!(:list_of_nonterminated_instances).and_return([
40
- {:instance_id => "i-5849ba", :ip => "ip-127-0-0-1.aws.amazon.com", :status => "running"},
41
- {:instance_id => "i-5849bb", :ip => "ip-127-0-0-2.aws.amazon.com", :status => "running"},
42
- {:instance_id => "i-5849bc", :ip => "ip-127-0-0-3.aws.amazon.com", :status => "pending"}
63
+ {:instance_id => "i-5849ba", :ip => "127.0.0.1", :status => "running"},
64
+ {:instance_id => "i-5849bb", :ip => "127.0.0.2", :status => "running"},
65
+ {:instance_id => "i-5849bc", :ip => "127.0.0.3", :status => "pending"}
43
66
  ])
44
67
  Kernel.stub!(:exec).and_return true
68
+ @instance = RemoteInstance.new
69
+ @instance.stub!(:ip).and_return("127.0.0.1")
70
+ @instance.stub!(:name).and_return("node0")
45
71
  end
46
72
 
47
73
  it "should be able to go through the instances and assign them numbers" do
@@ -61,72 +87,128 @@ describe "Master" do
61
87
  @master.get_node(2).instance_id.should == "i-5849bc"
62
88
  end
63
89
  it "should be able to build a hosts file" do
64
- open(@master.build_hosts_file.path).read.should == "ip-127-0-0-1.aws.amazon.com node0\nip-127-0-0-2.aws.amazon.com node1\nip-127-0-0-3.aws.amazon.com node2"
90
+ open(@master.build_hosts_file_for(@instance).path).read.should == "127.0.0.1 node0\n127.0.0.1 localhost.localdomain localhost ubuntu\n127.0.0.2 node1\n127.0.0.3 node2"
65
91
  end
66
92
  it "should be able to build a hosts file for a specific instance" do
67
- @master.build_hosts_file_for(@master.nodes.first).should =~ "127.0.0.1 node0"
93
+ open(@master.build_hosts_file_for(@instance).path).read.should =~ "127.0.0.1 node0"
68
94
  end
69
95
  it "should be able to build a haproxy file" do
70
- @master.build_haproxy_file.should =~ "server node0 ip-127-0-0-1.aws.amazon.com:#{Application.client_port}"
96
+ open(@master.build_haproxy_file.path).read.should =~ "server node0 127.0.0.1:#{Application.client_port}"
71
97
  end
72
98
  it "should be able to reconfigure the instances (working on two files a piece)" do
73
- @master.nodes[0].should_receive(:configure).and_return true if @master.nodes[0].status =~ /running/
99
+ @master.should_receive(:remote_configure_instances).and_return true
74
100
  @master.stub!(:number_of_unconfigured_nodes).and_return 1
75
- @master.reconfigure_running_instances
101
+ @master.reconfigure_cloud_when_necessary
102
+ end
103
+ it "should return the number of unconfigured nodes when asked" do
104
+ @master.nodes.each {|node| node.stub!(:stack_installed?).and_return(true) unless node.master? }
105
+ @master.number_of_unconfigured_nodes.should == 1
106
+ end
107
+ it "should be able to return the size of the cloud" do
108
+ @master.nodes.size.should == 3
76
109
  end
77
110
  it "should be able to restart the running instances' services" do
78
111
  @master.nodes.each {|a| a.should_receive(:restart_with_monit).and_return true }
79
112
  @master.restart_running_instances_services
80
113
  end
81
114
  it "should be able to build a heartbeat auth file" do
82
- open(@master.build_heartbeat_authkeys_file).read.should =~ /1 md5/
115
+ open(@master.build_and_copy_heartbeat_authkeys_file.path).read.should =~ /1 md5/
83
116
  end
84
- describe "configuring" do
85
- before(:each) do
86
- Master.stub!(:new).and_return(@master)
87
- end
88
- it "should be able to build a heartbeat resources file for the specific node" do
89
- Master.build_heartbeat_resources_file_for(@master.nodes.first).should =~ /node0 ip-127/
90
- end
91
- it "should be able to build a heartbeat config file" do
92
- Master.build_heartbeat_config_file_for(@master.nodes.first).should =~ /\nnode node0\nnode node1/
117
+ describe "configuration" do
118
+ describe "sending configuration files" do
119
+ before(:each) do
120
+ Master.stub!(:new).and_return(@master)
121
+ end
122
+ it "should be able to build a heartbeat resources file for the specific node" do
123
+ open(Master.build_heartbeat_resources_file_for(@master.nodes.first).path).read.should =~ /node0 127/
124
+ end
125
+ it "should be able to build a heartbeat config file" do
126
+ open(Master.build_heartbeat_config_file_for(@master.nodes.first).path).read.should =~ /\nnode node0\nnode node1/
127
+ end
128
+ it "should be able to say if heartbeat is necessary with more than 1 server or not" do
129
+ Master.requires_heartbeat?.should == true
130
+ end
131
+ it "should be able to say that heartbeat is not necessary if there is 1 server" do
132
+ @master.stub!(:list_of_nonterminated_instances).and_return([
133
+ {:instance_id => "i-5849ba", :ip => "127.0.0.1", :status => "running"}
134
+ ])
135
+ Master.requires_heartbeat?.should == false
136
+ end
137
+ it "should only install the stack on nodes that don't have it marked locally as installed" do
138
+ @master.nodes.each {|i| i.should_receive(:stack_installed?).and_return(true)}
139
+ @master.should_not_receive(:reconfigure_running_instances)
140
+ @master.reconfigure_cloud_when_necessary
141
+ end
142
+ it "should install the stack on all the nodes (because it needs reconfiguring) if there is any node that needs the stack" do
143
+ @master.nodes.first.should_receive(:stack_installed?).and_return(false)
144
+ @master.should_receive(:configure_cloud).once.and_return(true)
145
+ @master.reconfigure_cloud_when_necessary
146
+ end
147
+ describe "rsync'ing the files to the instances" do
148
+ it "should receive send_config_files_to_nodes after it builds the config files in the temp directory" do
149
+ @master.should_receive(:send_config_files_to_nodes).once
150
+ @master.build_and_send_config_files_in_temp_directory
151
+ end
152
+ it "should run_array_of_tasks(scp_tasks)" do
153
+ @master.should_receive(:run_array_of_tasks).and_return true
154
+ @master.build_and_send_config_files_in_temp_directory
155
+ end
156
+ it "should compile a list of files to rsync" do
157
+ @master.stub!(:run_array_of_tasks).and_return true
158
+ @master.rsync_tasks("#{@master.base_tmp_dir}", "tmp")[0].should =~ /rsync/
159
+ end
160
+ end
161
+ describe "remote configuration" do
162
+ before(:each) do
163
+ @master.stub!(:nodes).and_return [@instance]
164
+ end
165
+ it "should call remote_configure_instances when configuring" do
166
+ @master.should_receive(:remote_configure_instances).and_return true
167
+ @master.configure_cloud
168
+ end
169
+ it "should change the configuration script into an executable and run it" do
170
+ @master.should_receive(:run_array_of_tasks).and_return true
171
+ @master.remote_configure_instances
172
+ end
173
+ end
174
+
93
175
  end
94
- it "should be able to say if heartbeat is necessary with more than 1 server or not" do
95
- Master.requires_heartbeat?.should == true
96
- end
97
- it "should be able to say that heartbeat is not necessary if there is 1 server" do
98
- @master.stub!(:list_of_nonterminated_instances).and_return([
99
- {:instance_id => "i-5849ba", :ip => "ip-127-0-0-1.aws.amazon.com", :status => "running"}
100
- ])
101
- Master.requires_heartbeat?.should == false
102
- end
103
- it "should only install the stack on nodes that don't have it marked locally as installed" do
104
- @master.nodes.each {|i| i.should_receive(:stack_installed?).and_return(true)}
105
- @master.should_not_receive(:reconfigure_running_instances)
106
- @master.reconfigure_cloud_when_necessary
107
- end
108
- it "should install the stack on all the nodes (because it needs reconfiguring) if there is any node that needs the stack" do
109
- @master.nodes.first.should_receive(:stack_installed?).and_return(false)
110
- @master.should_receive(:reconfigure_running_instances).once.and_return(true)
111
- @master.reconfigure_cloud_when_necessary
112
- end
113
- describe "with new configuration and installation (build scripts)" do
176
+ end
177
+ describe "installation" do
178
+ it "should not install on the instances if the application doesn't say it should" do
179
+ Application.stub!(:install_on_load?).and_return false
180
+ Provider.should_not_receive(:install_poolparty)
181
+ @master.install_cloud
182
+ end
183
+ describe "when asked" do
114
184
  before(:each) do
115
- @node = @master.nodes.first
185
+ Application.stub!(:install_on_load?).and_return true
186
+ Sprinkle::Script.stub!(:sprinkle).and_return true
187
+ @master.stub!(:execute_tasks).and_return true
188
+ end
189
+ it "should install on the instances if the application says it should" do
190
+ Provider.should_receive(:install_poolparty)
191
+ @master.install_cloud
116
192
  end
117
- it "should be able to build_scp_instances_script_for" do
118
- @node.should_receive(:scp_string).exactly(10).times.and_return("true")
119
- Master.build_scp_instances_script_for(@node)
193
+ it "should execute the remote tasks on all of the instances" do
194
+ @master.should_receive(:execute_tasks).and_return true
195
+ @master.install_cloud
120
196
  end
121
- it "should be able to build_scp_instances_script_for and contain scp 10 times" do
122
- open(Master.build_scp_instances_script_for(@node)).read.scan(/scp/).size.should == 10
197
+ describe "stubbing installation" do
198
+ before(:each) do
199
+ @master.stub!(:execute_tasks).and_return true
200
+ end
201
+ it "should install poolparty" do
202
+ Provider.should_receive(:install_poolparty).and_return true
203
+ Provider.should_receive(:install_userpackages).and_return true
204
+ @master.install_cloud
205
+ end
206
+ it "should install the user packages" do
207
+ Provider.should_receive(:install_poolparty).and_return true
208
+ Provider.should_receive(:install_userpackages).and_return true
209
+ @master.install_cloud
210
+ end
123
211
  end
124
- it "should be able to build_reconfigure_instances_script_for" do
125
- str = open(Master.build_reconfigure_instances_script_for(@node)).read
126
- str.should =~ /hostname -v node0/
127
- str.should =~ /mkdir \/etc\/ha\.d\/resource\.d/
128
- str.should =~ /pool\ maintain\ \-c \~\/\.config/
129
- end
130
212
  end
131
213
  end
132
214
  describe "displaying" do
@@ -134,6 +216,20 @@ describe "Master" do
134
216
  @master.list.should =~ /CLOUD \(/
135
217
  end
136
218
  end
219
+ it "should be able to grab a list of the instances" do
220
+ @master.cloud_ips.should == %w(127.0.0.1 127.0.0.2 127.0.0.3)
221
+ end
222
+ describe "starting" do
223
+ before(:each) do
224
+ Kernel.stub!(:sleep).and_return true
225
+ end
226
+ it "should request to launch the minimum number of instances" do
227
+ Application.stub!(:minimum_instances).and_return 3
228
+ @master.stub!(:number_of_pending_and_running_instances).and_return 1
229
+ @master.should_receive(:request_launch_new_instances).with(2).and_return true
230
+ @master.launch_minimum_instances
231
+ end
232
+ end
137
233
  describe "monitoring" do
138
234
  it "should start the monitor when calling start_monitor!" do
139
235
  @master.should_receive(:run_thread_loop).and_return(Proc.new {})
@@ -157,7 +253,7 @@ describe "Master" do
157
253
  @master.contract?.should == false
158
254
  end
159
255
  it "should be able to say that it should contract" do
160
- @master.stub!(:web).and_return(30.2)
256
+ @master.stub!(:web).and_return(31.2)
161
257
  @master.stub!(:cpu).and_return(0.05)
162
258
 
163
259
  @master.contract?.should == true
@@ -174,30 +270,158 @@ describe "Master" do
174
270
 
175
271
  @master.expand?.should == true
176
272
  end
273
+ describe "scaling" do
274
+ before(:each) do
275
+ Kernel.stub!(:sleep).and_return true
276
+ @master.stub!(:wait_for_all_instances_to_boot).and_return true
277
+ @master.stub!(:wait_for_all_instances_to_terminate).and_return true
278
+ end
279
+ it "should try to add a new instance" do
280
+ @master.should_receive(:add_instance_if_load_is_high).and_return true
281
+ @master.scale_cloud!
282
+ end
283
+ it "should try to terminate an instance" do
284
+ @master.should_receive(:terminate_instance_if_load_is_low).and_return true
285
+ @master.scale_cloud!
286
+ end
287
+ it "should try to grow the cloud by 1 node when asked" do
288
+ @master.should_receive(:request_launch_new_instances).once.and_return true
289
+ @master.should_receive(:configure_cloud).once.and_return true
290
+ @master.grow_by(1)
291
+ end
292
+ it "should try to shrink the cloud by 1 when asked" do
293
+ @master.should_receive(:request_termination_of_instance).and_return true
294
+ @master.should_receive(:configure_cloud).and_return true
295
+ @master.shrink_by(1)
296
+ end
297
+ end
177
298
  end
178
- end
179
- describe "Configuration" do
180
- it "should be able to build the haproxy file" do
181
- @master.build_haproxy_file
182
- end
183
- end
184
- describe "Singleton methods" do
185
- before(:each) do
186
- @master = Master.new
187
- @instance = RemoteInstance.new
188
- @blk = Proc.new {puts "new"}
189
- Master.stub!(:new).once.and_return @master
190
- end
191
- it "should be able to run with_nodes" do
192
- Master.should_receive(:new).once.and_return @master
193
- @master.should_receive(:nodes).once.and_return []
194
- Master.with_nodes &@blk
195
- end
196
- it "should run the block on each node" do
197
- collection = [@instance]
198
- @master.should_receive(:nodes).once.and_return collection
199
- collection.should_receive(:each).once
200
- Master.with_nodes &@blk
299
+ describe "Configuration" do
300
+ before(:each) do
301
+ @instance = RemoteInstance.new
302
+ end
303
+ it "should be able to build the haproxy file" do
304
+ @master.build_haproxy_file
305
+ end
306
+ describe "by copying files to the poolpartytmp directory" do
307
+ it "should build and copy files to the tmp directory" do
308
+ @master.build_and_send_config_files_in_temp_directory
309
+ File.directory?(@master.base_tmp_dir).should == true
310
+ end
311
+ it "should copy the cloud_master_takeover script to the tmp directory" do
312
+ @master.should_receive(:get_config_file_for).at_least(1).and_return "true"
313
+ File.should_receive(:copy).exactly(3).and_return true
314
+ @master.build_and_send_config_files_in_temp_directory
315
+ end
316
+ describe "get configs" do
317
+ before(:each) do
318
+ @master.stub!(:user_dir).and_return("user")
319
+ @master.stub!(:root_dir).and_return("root")
320
+ end
321
+ it "should try to get the config file in the user directory before the root_dir" do
322
+ File.should_receive(:exists?).with("#{@master.user_dir}/config/config.yml").and_return true
323
+ @master.get_config_file_for("config.yml").should == "user/config/config.yml"
324
+ end
325
+ it "should try to get the config file in the root directory if it doesn't exist in the user directory" do
326
+ File.should_receive(:exists?).with("#{@master.user_dir}/config/config.yml").and_return false
327
+ @master.get_config_file_for("config.yml").should == "root/config/config.yml"
328
+ end
329
+ end
330
+ it "should copy the config file if it exists" do
331
+ Application.stub!(:config_file).and_return "config.yml"
332
+ File.stub!(:exists?).and_return true
333
+ File.should_receive(:copy).exactly(5).times.and_return true
334
+ @master.build_and_send_config_files_in_temp_directory
335
+ end
336
+ describe "with copy_config_files_in_directory_to_tmp_dir method" do
337
+ before(:each) do
338
+ @instance2 = RemoteInstance.new
339
+ @instance2.stub!(:ip).and_return "127.0.0.2"
340
+ Master.stub!(:new).and_return @master
341
+ @master.stub!(:nodes).and_return [@instance, @instance2]
342
+ end
343
+ it "should be able to clean up after itself" do
344
+ File.open("#{@master.base_tmp_dir}/test", "w+") {|f| f << "hello world"}
345
+ @master.cleanup_tmp_directory(nil)
346
+ File.file?("#{@master.base_tmp_dir}/test").should == false
347
+ end
348
+ it "should check to see if there is a directory in the user directory to grab the files from" do
349
+ File.stub!(:directory?).with("/Users/auser/Sites/work/citrusbyte/internal/gems/poolparty/pool/tmp/resource.d").and_return false
350
+ File.should_receive(:directory?).at_least(1).with("#{user_dir}/config/resource.d").at_least(1).and_return true
351
+ @master.copy_config_files_in_directory_to_tmp_dir("config/resource.d")
352
+ end
353
+ it "should copy all the files that are in the directory" do
354
+ Dir.stub!(:[]).and_return ["1","2","3"]
355
+ File.should_receive(:copy).exactly(3).times.and_return true
356
+ @master.copy_config_files_in_directory_to_tmp_dir("config/resource.d")
357
+ end
358
+ it "should copy all the resource.d files from the monit directory to the tmp directory" do
359
+ @master.stub!(:copy_config_files_in_directory_to_tmp_dir).with("config/resource.d").and_return true
360
+ @master.should_receive(:copy_config_files_in_directory_to_tmp_dir).at_least(1).with("config/monit.d").and_return true
361
+ @master.build_and_send_config_files_in_temp_directory
362
+ end
363
+ it "should build the authkeys file for haproxy" do
364
+ @master.should_receive(:build_and_copy_heartbeat_authkeys_file).and_return true
365
+ @master.build_and_send_config_files_in_temp_directory
366
+ end
367
+ it "should build the haproxy configuration file" do
368
+ @master.should_receive(:build_haproxy_file).and_return true
369
+ @master.build_and_send_config_files_in_temp_directory
370
+ end
371
+ it "should build the hosts file for nodes" do
372
+ @master.should_receive(:build_hosts_file_for).at_least(1).and_return true
373
+ @master.build_and_send_config_files_in_temp_directory
374
+ end
375
+ it "should build the ssh reconfigure script" do
376
+ @master.should_receive(:build_reconfigure_instances_script_for).at_least(1).and_return ""
377
+ @master.build_and_send_config_files_in_temp_directory
378
+ end
379
+ it "should be able to build the hosts file for the nodes" do
380
+ @master.build_and_send_config_files_in_temp_directory
381
+ end
382
+ it "should build global files" do
383
+ Master.should_receive(:build_user_global_files).once
384
+ @master.build_and_send_config_files_in_temp_directory
385
+ end
386
+ it "should build user node files" do
387
+ Master.should_receive(:build_user_node_files_for).at_least(1)
388
+ @master.build_and_send_config_files_in_temp_directory
389
+ end
390
+ describe "when the cloud requires heartbeat" do
391
+ before(:each) do
392
+ Master.stub!(:requires_heartbeat?).and_return true
393
+ end
394
+ it "should build the heartbeat configuration file" do
395
+ @master.should_receive(:build_heartbeat_config_file_for).at_least(1).and_return true
396
+ @master.build_and_send_config_files_in_temp_directory
397
+ end
398
+ end
399
+ end
400
+ describe "user define files" do
401
+ it "should have access to global_user_files as an array" do
402
+ Master.global_user_files.class.should == Array
403
+ end
404
+ it "should be able to add a global file to the array" do
405
+ Master.define_global_user_file(:box) {"box"}
406
+ Master.global_user_files.size.should == 1
407
+ end
408
+ it "should have access to user_node_files as an array" do
409
+ Master.user_node_files.class.should == Array
410
+ end
411
+ it "should be able to add a node file to the array" do
412
+ Master.define_node_user_file(:box) {|a| "#{a}.box"}
413
+ Master.user_node_files.size.should == 1
414
+ end
415
+ it "should write_to_file_for for each of the global_user_files" do
416
+ Master.should_receive(:write_to_file_for).once.and_return true
417
+ Master.build_user_global_files
418
+ end
419
+ it "should write_to_file_for for each of the user_node_files" do
420
+ Master.should_receive(:write_to_file_for).once.and_return true
421
+ Master.build_user_node_files_for(@instance)
422
+ end
423
+ end
424
+ end
201
425
  end
202
426
  end
203
427
  end