dnapi 1.1.74.jruby192.c

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/lib/dnapi.rb +54 -0
  2. data/lib/dnapi/app.rb +130 -0
  3. data/lib/dnapi/component.rb +50 -0
  4. data/lib/dnapi/component_possessor.rb +49 -0
  5. data/lib/dnapi/components/addons.rb +26 -0
  6. data/lib/dnapi/components/apache.rb +13 -0
  7. data/lib/dnapi/components/cloudkick.rb +13 -0
  8. data/lib/dnapi/components/encrypted_backup.rb +12 -0
  9. data/lib/dnapi/components/exim.rb +10 -0
  10. data/lib/dnapi/components/monitor.rb +12 -0
  11. data/lib/dnapi/components/nagios.rb +28 -0
  12. data/lib/dnapi/components/newrelic.rb +12 -0
  13. data/lib/dnapi/components/passenger3.rb +13 -0
  14. data/lib/dnapi/components/ruby.rb +234 -0
  15. data/lib/dnapi/components/ssmtp.rb +10 -0
  16. data/lib/dnapi/components/stunneled.rb +13 -0
  17. data/lib/dnapi/components/volume.rb +32 -0
  18. data/lib/dnapi/cron.rb +5 -0
  19. data/lib/dnapi/db_stack.rb +92 -0
  20. data/lib/dnapi/ebuild_dep.rb +5 -0
  21. data/lib/dnapi/environment.rb +327 -0
  22. data/lib/dnapi/extensions.rb +32 -0
  23. data/lib/dnapi/gem_dep.rb +9 -0
  24. data/lib/dnapi/instance.rb +69 -0
  25. data/lib/dnapi/monitoring.rb +22 -0
  26. data/lib/dnapi/recipe.rb +27 -0
  27. data/lib/dnapi/ssl_cert.rb +13 -0
  28. data/lib/dnapi/stack.rb +111 -0
  29. data/lib/dnapi/struct.rb +149 -0
  30. data/lib/dnapi/test.rb +114 -0
  31. data/lib/dnapi/test/ext.rb +32 -0
  32. data/lib/dnapi/test/sweatshop.rb +148 -0
  33. data/lib/dnapi/version.rb +3 -0
  34. data/lib/dnapi/vhost.rb +24 -0
  35. data/spec/app_spec.rb +68 -0
  36. data/spec/component_spec.rb +66 -0
  37. data/spec/components/addons_spec.rb +33 -0
  38. data/spec/components/cloudkick_spec.rb +17 -0
  39. data/spec/components/nagios_spec.rb +42 -0
  40. data/spec/components/nodejs_spec.rb +27 -0
  41. data/spec/components/passenger3_spec.rb +12 -0
  42. data/spec/components/ruby_spec.rb +321 -0
  43. data/spec/components/stunneled.rb +15 -0
  44. data/spec/components/volume_spec.rb +21 -0
  45. data/spec/db_stack_spec.rb +111 -0
  46. data/spec/environment_spec.rb +227 -0
  47. data/spec/instance_spec.rb +52 -0
  48. data/spec/proxies.rb +143 -0
  49. data/spec/proxies_spec.rb +76 -0
  50. data/spec/spec_helper.rb +2 -0
  51. data/spec/stack_spec.rb +105 -0
  52. data/spec/struct_spec.rb +100 -0
  53. metadata +181 -0
@@ -0,0 +1,227 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe DNApi::Environment do
4
+
5
+ def instance_info
6
+ @id ||= 0
7
+ @id += 1
8
+ {:id => @id,
9
+ :public_hostname => "public_host.#{@id}",
10
+ :private_hostname => "private_host.#{@id}"}
11
+ end
12
+
13
+ def legacy_provision_instance
14
+ require 'ostruct'
15
+ OpenStruct.new(instance_info)
16
+ end
17
+
18
+ describe "#db_host" do
19
+ it 'is the public hostname of the solo instance if the environment consists of a solo instance' do
20
+ environment = DNApi::Environment.new
21
+ environment.build_instance(instance_info.merge(:role => :solo))
22
+ environment.db_host.should eql('public_host.1')
23
+ end
24
+
25
+ it 'is the public hostname of the database server' do
26
+ environment = DNApi::Environment.new
27
+ db_instance_info = instance_info
28
+ environment.build_instance(db_instance_info.merge(:role => :db_master))
29
+ environment.db_host.should eql(db_instance_info[:public_hostname])
30
+ end
31
+
32
+ it "responds to legacy 'api'" do
33
+ environment = DNApi::Environment.new
34
+ instance = legacy_provision_instance
35
+ environment.build_instance(:role => :db_master, :provisioned => instance)
36
+ environment.db_host.should eql(instance.public_hostname)
37
+ end
38
+ end
39
+
40
+ describe "#db_slaves" do
41
+ it 'is an array of all instances with the db_slave role' do
42
+ environment = DNApi::Environment.new
43
+ environment.build_instance(instance_info.merge(:role => :app_master))
44
+ environment.build_instance(instance_info.merge(:role => :db_master))
45
+ environment.build_instance(instance_info.merge(:role => :db_slave))
46
+ environment.build_instance(instance_info.merge(:role => :db_slave))
47
+ environment.build_instance(instance_info.merge(:role => :db_slave))
48
+ environment.should have(3).db_slaves
49
+ end
50
+ end
51
+
52
+ describe "#utility_instances" do
53
+ it 'is an array of all instances with the utility_instances role' do
54
+ environment = DNApi::Environment.new
55
+ environment.build_instance(instance_info.merge(:role => :app_master))
56
+ environment.build_instance(instance_info.merge(:role => :db_master))
57
+ environment.build_instance(instance_info.merge(:role => :db_slave))
58
+ environment.build_instance(instance_info.merge(:role => :util, :name => "foo"))
59
+ environment.build_instance(instance_info.merge(:role => :util, :name => "bar"))
60
+ environment.should have(2).utility_instances
61
+ end
62
+ end
63
+
64
+ describe '#recipes' do
65
+ it 'includes the stack recipes' do
66
+ env = DNApi::Environment.new(:stack => DNApi::Stack::NginxMongrel)
67
+ env.build_app
68
+ env.recipes.should include(*DNApi::Stack::NginxMongrel.recipes)
69
+ end
70
+
71
+ it 'includes the app recipes' do
72
+ env = DNApi::Environment.new(:stack => DNApi::Stack::NginxMongrel)
73
+ env.build_app
74
+ env.recipes.should include(*DNApi::Recipe.defaults)
75
+ end
76
+ end
77
+
78
+ describe '#mysql_backup_cron' do
79
+ it 'is nil if there is no backup_window' do
80
+ env = DNApi::Environment.new(:backup_window => 0, :db_stack => DNApi::DbStack::Mysql)
81
+ env.mysql_backup_cron.should be_nil
82
+ end
83
+
84
+ it 'runs eybackup --quiet' do
85
+ env = DNApi::Environment.new(:backup_window => 1, :db_stack => DNApi::DbStack::Mysql)
86
+ env.mysql_backup_cron.command.should == 'eybackup --quiet'
87
+ end
88
+
89
+ it 'is 10 past the hour' do
90
+ env = DNApi::Environment.new(:backup_window => 1, :db_stack => DNApi::DbStack::Mysql)
91
+ env.mysql_backup_cron.minute.should == '10'
92
+ end
93
+
94
+ it 'is at 1 AM PST if the backup interal is 24 hours' do
95
+ env = DNApi::Environment.new(:backup_window => 1, :backup_interval => 24, :db_stack => DNApi::DbStack::Mysql)
96
+ env.mysql_backup_cron.hour.should == '1'
97
+ end
98
+
99
+ it 'is at every other hour if the backup interval is 12 hours' do
100
+ env = DNApi::Environment.new(:backup_window => 1, :backup_interval => 12, :db_stack => DNApi::DbStack::Mysql)
101
+ env.mysql_backup_cron.hour.should == '*/12'
102
+ end
103
+ end
104
+
105
+ describe '#snapshot_cron' do
106
+ it 'runs ey-snapshots --snapshot --quiet' do
107
+ env = DNApi::Environment.new(:backup_window => 1)
108
+ env.snapshot_cron.command.should == 'ey-snapshots --snapshot --quiet'
109
+ end
110
+ it 'is in the last half of the hour' do
111
+ env = DNApi::Environment.new(:backup_window => 1)
112
+ env.snapshot_cron.minute.should =~ %r{0/[3-5]\d}
113
+ end
114
+ end
115
+
116
+ describe '#postgres_backup_cron' do
117
+ it 'runs eybackup -e postgresql --quiet' do
118
+ env = DNApi::Environment.new(:backup_window => 1, :db_stack => DNApi::DbStack::Postgres)
119
+ env.postgres_backup_cron.command.should == 'eybackup -e postgresql --quiet'
120
+ end
121
+
122
+ it 'is 20 past the hour' do
123
+ env = DNApi::Environment.new(:backup_window => 1, :db_stack => DNApi::DbStack::Postgres)
124
+ env.postgres_backup_cron.minute.should == '20'
125
+ end
126
+
127
+ context "disabled backups" do
128
+ it 'returns nil when backups are disabled on Postgres DbStack' do
129
+ env = DNApi::Environment.new(:backup_window => 0, :db_stack => DNApi::DbStack::Postgres)
130
+ env.postgres_backup_cron.should be_nil
131
+ end
132
+
133
+ it 'returns nil when backups are disabled on Postgres9 DbStack' do
134
+ env = DNApi::Environment.new(:backup_window => 0, :db_stack => DNApi::DbStack::Postgres9)
135
+ env.postgres_backup_cron.should be_nil
136
+ end
137
+
138
+ it 'returns nil when backups are disabled on Postgres91 DbStack' do
139
+ env = DNApi::Environment.new(:backup_window => 0, :db_stack => DNApi::DbStack::Postgres91)
140
+ env.postgres_backup_cron.should be_nil
141
+ end
142
+ end
143
+ end
144
+
145
+ it "has newrelic" do
146
+ env = DNApi::Environment.new
147
+
148
+ env.newrelic_key = "foo"
149
+ env.newrelic_key.should == "foo"
150
+
151
+ env.build_app(:newrelic => true)
152
+ env.apps.first.newrelic?.should be_true
153
+
154
+ env.build_app.newrelic?.should be_false
155
+ end
156
+
157
+ it "ignores disabled instances when listing enabled app servers hostnames" do
158
+ env = DNApi.gen(:default, 'nginx_passenger', ['rails'])
159
+ env.build_instance(instance_info.merge(:role => :db_master, :enabled => false))
160
+ env.build_instance(instance_info.merge(:role => :app_master, :enabled => false))
161
+ instance = env.build_instance(instance_info.merge(:role => :app, :enabled => false))
162
+ env.enabled_app_servers_hostnames.should be_empty
163
+
164
+ dna = JSON.parse(instance.to_dna.to_json)
165
+ dna["members"].should be_empty
166
+ end
167
+
168
+ describe '#backup_window' do
169
+ it 'should default to 14' do
170
+ DNApi::Environment.new.backup_window.should == 14
171
+ end
172
+ end
173
+
174
+ describe "#dna_for_instance" do
175
+ it "raises an error when the instance is not found" do
176
+ env = DNApi.gen(:default, 'nginx_passenger', [])
177
+ instance = env.build_instance(instance_info.merge(:role => :solo))
178
+ lambda { DNApi::Environment.new.dna_for_instance(instance) }.
179
+ should raise_error(DNApi::InstanceNotFound)
180
+ end
181
+ end
182
+
183
+ specify "monitoring" do
184
+ default = DNApi::Environment.new
185
+ default.monitoring.should be_monit
186
+ default.monitoring = "god"
187
+ default.monitoring.should be_god
188
+
189
+ monit = DNApi::Environment.new(:monitoring => "monit").monitoring
190
+ monit.should be_monit
191
+ monit.name.should == "monit"
192
+ monit.label.should == "Monit"
193
+
194
+ god = DNApi::Environment.new(:monitoring => "god").monitoring
195
+ god.should be_god
196
+ god.name.should == "god"
197
+ god.label.should == "god"
198
+ end
199
+
200
+ specify "components" do
201
+ env = DNApi::Environment.new
202
+ env.components.should be_empty
203
+ env.components << DNApi::Component[:god].new
204
+ env.has_component?(:god).should be_true
205
+ end
206
+
207
+ describe "#components" do
208
+ it 'dumps components into a hash' do
209
+ env = DNApi::Environment.new
210
+ env.add_component(:god)
211
+ DNApi::Environment.from(JSON.parse(env.to_json)).components.should_not be_empty
212
+ end
213
+ end
214
+
215
+ describe "#ruby_component" do
216
+ before(:each) do
217
+ @env = DNApi::Environment.new
218
+ end
219
+
220
+ DNApi::Components::RubyVersion.all.each do |ruby_component|
221
+ it "returns #{ruby_component.key_id} when appropriate" do
222
+ @env.add_component(ruby_component.key_id)
223
+ @env.ruby_component.should == @env.component(ruby_component.key_id)
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe DNApi::Instance do
4
+ before(:each) do
5
+ @environment = DNApi::Environment.new
6
+ @instance = @environment.build_instance(:id => 'default', :role => :app_master)
7
+ end
8
+
9
+ it 'has selectors for environment data' do
10
+ @instance.apps.should == @environment.apps
11
+ @instance.recipes.should == @environment.recipes
12
+ @instance.solo?.should == @environment.solo?
13
+ @instance.ssh_username == @environment.ssh_username
14
+ @instance.ssh_password == @environment.ssh_password
15
+ end
16
+
17
+ it "has newrelic attributes when it has the :newrelic component" do
18
+ @instance.add_component(:newrelic, :seed => 'bar', :token => 'sekretz')
19
+
20
+ @instance.newrelic_seed.should == "bar"
21
+ @instance.newrelic_token.should == "sekretz"
22
+ end
23
+
24
+ it "lacks newrelic attributes when it lacks the :newrelic component" do
25
+ @instance.should_not have_component(:newrelic)
26
+ @instance.newrelic_token.should be_nil
27
+ end
28
+
29
+ it "allows adding a ssmtp component" do
30
+ @instance.add_component(:ssmtp)
31
+ @instance.should have_component(:ssmtp)
32
+ end
33
+
34
+ it "allows adding a ssmtp component" do
35
+ @instance.add_component(:exim, :user => "test", :password => "suck", :host => "example.org", :outbound_host => "sendgrid")
36
+
37
+ @instance.should have_component(:exim)
38
+
39
+ exim = @instance.component(:exim)
40
+ exim.user.should == "test"
41
+ exim.password.should == "suck"
42
+ exim.host.should == "example.org"
43
+ exim.outbound_host.should == "sendgrid"
44
+ end
45
+
46
+ it 'allows adding multiple volume components' do
47
+ @instance.add_component(:ebs_volume, :mountpoint => '/data', :size => 5 * 1024 * 1024)
48
+ @instance.add_component(:ebs_volume, :mountpoint => '/db', :size => 5 * 1024 * 1024)
49
+
50
+ @instance.volumes.size.should == 2
51
+ end
52
+ end
data/spec/proxies.rb ADDED
@@ -0,0 +1,143 @@
1
+ class Object
2
+ def deep_clone
3
+ Marshal.load(Marshal.dump(self))
4
+ end
5
+ end
6
+
7
+ module DNApi
8
+ class ArrayProxy
9
+ def self.build(array)
10
+ new(array).build
11
+ end
12
+
13
+ def initialize(array)
14
+ @array = array
15
+ end
16
+
17
+ def build
18
+ @positions = (0..(@array.size - 1)).to_a
19
+ @original_array = @array.deep_clone
20
+ @array.map! do |value|
21
+ case value
22
+ when Hash
23
+ HashProxy.build(value)
24
+ when Array
25
+ ArrayProxy.build(value)
26
+ else
27
+ value
28
+ end
29
+ end
30
+ self
31
+ end
32
+
33
+ def index_of(&block)
34
+ @original_array.each_with_index do |value,i|
35
+ return i if yield value
36
+ end
37
+ raise "Could not find the element in #{inspect}"
38
+ end
39
+
40
+ def size
41
+ @array.size
42
+ end
43
+
44
+ def empty?
45
+ @array.empty?
46
+ end
47
+
48
+ def fully_accessed?
49
+ @positions.empty? && unaccessed_children.empty?
50
+ end
51
+
52
+ def unaccessed
53
+ {:positions => @positions, :children => unaccessed_children}
54
+ end
55
+
56
+ def unaccessed_children
57
+ foo = {}
58
+ @array.each_with_index do |value,i|
59
+ case value
60
+ when HashProxy, ArrayProxy
61
+ foo[i] = value.unaccessed unless value.fully_accessed?
62
+ when Hash, Array
63
+ raise "This should be a #{value.class}Proxy: #{value.inspect}"
64
+ end
65
+ end
66
+ foo
67
+ end
68
+
69
+ def [](position)
70
+ @positions.delete(position)
71
+ @array[position]
72
+ end
73
+
74
+ def inspect
75
+ @array.inspect
76
+ end
77
+ end
78
+
79
+ class HashProxy
80
+ def self.build(hash)
81
+ new(hash).build
82
+ end
83
+
84
+ def initialize(hash)
85
+ @hash = hash
86
+ end
87
+
88
+ def build
89
+ @keys = @hash.keys
90
+ @hash.each do |key,value|
91
+ case value
92
+ when Hash
93
+ @hash[key] = HashProxy.build(value)
94
+ when Array
95
+ @hash[key] = ArrayProxy.build(value)
96
+ end
97
+ end
98
+ self
99
+ end
100
+
101
+ def has_key?(key)
102
+ @hash.has_key?(key)
103
+ end
104
+
105
+ def keys
106
+ @hash.keys
107
+ end
108
+
109
+ def any?
110
+ @hash.any?
111
+ end
112
+
113
+ def fully_accessed?
114
+ @keys.empty? && unaccessed_children.empty?
115
+ end
116
+
117
+ def unaccessed
118
+ {:keys => @keys, :children => unaccessed_children}
119
+ end
120
+
121
+ def unaccessed_children
122
+ foo = {}
123
+ @hash.each do |key,value|
124
+ case value
125
+ when HashProxy, ArrayProxy
126
+ foo[key] = value.unaccessed unless value.fully_accessed?
127
+ when Hash, Array
128
+ raise "This should be a #{value.class}Proxy: #{key.inspect} #{value.inspect}"
129
+ end
130
+ end
131
+ foo
132
+ end
133
+
134
+ def [](key)
135
+ @keys.delete(key)
136
+ @hash[key]
137
+ end
138
+
139
+ def inspect
140
+ @hash.inspect
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/proxies'
2
+ require 'pp'
3
+ require 'sexp_processor' #defines Object#deep_copy
4
+
5
+ describe DNApi::HashProxy do
6
+ describe "with no elements" do
7
+ it "is fully accessed" do
8
+ DNApi::HashProxy.build({}).should be_fully_accessed
9
+ end
10
+ end
11
+
12
+ describe "with a single element" do
13
+ before(:each) do
14
+ @hash = DNApi::HashProxy.build({:foo => :bar})
15
+ end
16
+
17
+ it "is not fully accessed" do
18
+ @hash.should_not be_fully_accessed
19
+ end
20
+
21
+ describe "which has been accessed" do
22
+ it "is fully accessed" do
23
+ @hash[:foo]
24
+ @hash.should be_fully_accessed
25
+ end
26
+ end
27
+ end
28
+
29
+ describe "with a nested hash" do
30
+ it "should not be fully accessed unless it's children have been fully accessed" do
31
+ @hash = DNApi::HashProxy.build({:foo => {:bar => :baz}})
32
+ lambda { @hash[:foo] }.should_not change { @hash.fully_accessed? }
33
+ @hash.unaccessed[:keys].should be_empty
34
+ @hash.unaccessed[:children].should == {:foo => {:keys => [:bar], :children => {}}}
35
+ @hash[:foo][:bar]
36
+ @hash.should be_fully_accessed
37
+ end
38
+ end
39
+
40
+ describe "with a nested array" do
41
+ it "should not be fully accessed until it's elements have been accessed" do
42
+ @hash = DNApi::HashProxy.build({:foo => [:bar, :baz]})
43
+ lambda { @hash[:foo] }.should_not change { @hash.fully_accessed? }
44
+ @hash.unaccessed[:keys].should be_empty
45
+ @hash.unaccessed[:children].should == {:foo => {:positions => [0, 1], :children => {}}}
46
+ @hash[:foo][0]
47
+ @hash[:foo][1]
48
+ @hash.should be_fully_accessed
49
+ end
50
+ end
51
+ end
52
+
53
+ describe DNApi::ArrayProxy do
54
+ describe "with no elements" do
55
+ it "is fully accessed" do
56
+ DNApi::ArrayProxy.build([]).should be_fully_accessed
57
+ end
58
+ end
59
+
60
+ describe "with a single element" do
61
+ before(:each) do
62
+ @array = DNApi::ArrayProxy.build([:foo])
63
+ end
64
+
65
+ it "is not fully accessed" do
66
+ @array.should_not be_fully_accessed
67
+ end
68
+
69
+ describe "which has been accessed" do
70
+ it "is fully accessed" do
71
+ @array[0]
72
+ @array.should be_fully_accessed
73
+ end
74
+ end
75
+ end
76
+ end