right_support 2.6.17 → 2.7.0

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 (83) hide show
  1. data/.rspec +4 -0
  2. data/CHANGELOG.rdoc +37 -0
  3. data/Gemfile +29 -0
  4. data/Gemfile.lock +111 -0
  5. data/README.rdoc +2 -0
  6. data/Rakefile +62 -0
  7. data/VERSION +1 -0
  8. data/features/balancer_error_handling.feature +34 -0
  9. data/features/balancer_health_check.feature +33 -0
  10. data/features/continuous_integration.feature +51 -0
  11. data/features/continuous_integration_cucumber.feature +28 -0
  12. data/features/continuous_integration_rspec1.feature +28 -0
  13. data/features/continuous_integration_rspec2.feature +28 -0
  14. data/features/http_client_timeout.feature +19 -0
  15. data/features/serialization.feature +95 -0
  16. data/features/step_definitions/http_client_steps.rb +27 -0
  17. data/features/step_definitions/request_balancer_steps.rb +93 -0
  18. data/features/step_definitions/ruby_steps.rb +176 -0
  19. data/features/step_definitions/serialization_steps.rb +96 -0
  20. data/features/step_definitions/server_steps.rb +134 -0
  21. data/features/support/env.rb +138 -0
  22. data/features/support/file_utils_bundler_mixin.rb +45 -0
  23. data/lib/right_support/ci/java_cucumber_formatter.rb +22 -8
  24. data/lib/right_support/ci/java_spec_formatter.rb +26 -8
  25. data/lib/right_support/ci/rake_task.rb +3 -0
  26. data/lib/right_support/ci.rb +24 -0
  27. data/lib/right_support/crypto/signed_hash.rb +22 -0
  28. data/lib/right_support/data/serializer.rb +24 -2
  29. data/lib/right_support/net/address_helper.rb +20 -8
  30. data/lib/right_support/net/dns.rb +20 -8
  31. data/lib/right_support/net/http_client.rb +22 -0
  32. data/lib/right_support/net/request_balancer.rb +27 -21
  33. data/lib/right_support/net/s3_helper.rb +20 -8
  34. data/lib/right_support/net/ssl/open_ssl_patch.rb +22 -0
  35. data/lib/right_support/net/ssl.rb +20 -8
  36. data/lib/right_support/ruby/easy_singleton.rb +22 -0
  37. data/lib/right_support/ruby/object_extensions.rb +22 -0
  38. data/lib/right_support/ruby/string_extensions.rb +1 -1
  39. data/lib/right_support.rb +13 -10
  40. data/right_support.gemspec +180 -18
  41. data/right_support.rconf +8 -0
  42. data/spec/config/feature_set_spec.rb +83 -0
  43. data/spec/crypto/signed_hash_spec.rb +60 -0
  44. data/spec/data/hash_tools_spec.rb +471 -0
  45. data/spec/data/uuid_spec.rb +45 -0
  46. data/spec/db/cassandra_model_part1_spec.rb +84 -0
  47. data/spec/db/cassandra_model_part2_spec.rb +73 -0
  48. data/spec/db/cassandra_model_spec.rb +359 -0
  49. data/spec/fixtures/encrypted_priv_rsa.pem +30 -0
  50. data/spec/fixtures/good_priv_dsa.pem +12 -0
  51. data/spec/fixtures/good_priv_rsa.pem +15 -0
  52. data/spec/fixtures/good_pub_dsa.ssh +1 -0
  53. data/spec/fixtures/good_pub_rsa.pem +5 -0
  54. data/spec/fixtures/good_pub_rsa.ssh +1 -0
  55. data/spec/log/exception_logger_spec.rb +76 -0
  56. data/spec/log/filter_logger_spec.rb +8 -0
  57. data/spec/log/mixin_spec.rb +62 -0
  58. data/spec/log/multiplexer_spec.rb +54 -0
  59. data/spec/log/null_logger_spec.rb +36 -0
  60. data/spec/log/system_logger_spec.rb +92 -0
  61. data/spec/net/address_helper_spec.rb +57 -0
  62. data/spec/net/balancing/health_check_spec.rb +382 -0
  63. data/spec/net/balancing/round_robin_spec.rb +15 -0
  64. data/spec/net/balancing/sticky_policy_spec.rb +92 -0
  65. data/spec/net/dns_spec.rb +152 -0
  66. data/spec/net/http_client_spec.rb +171 -0
  67. data/spec/net/request_balancer_spec.rb +579 -0
  68. data/spec/net/s3_helper_spec.rb +160 -0
  69. data/spec/net/ssl_spec.rb +42 -0
  70. data/spec/net/string_encoder_spec.rb +58 -0
  71. data/spec/rack/log_setter_spec.rb +5 -0
  72. data/spec/rack/request_logger_spec.rb +68 -0
  73. data/spec/rack/request_tracker_spec.rb +5 -0
  74. data/spec/ruby/easy_singleton_spec.rb +72 -0
  75. data/spec/ruby/object_extensions_spec.rb +27 -0
  76. data/spec/ruby/string_extensions_spec.rb +98 -0
  77. data/spec/spec_helper.rb +181 -0
  78. data/spec/stats/activity_spec.rb +193 -0
  79. data/spec/stats/exceptions_spec.rb +123 -0
  80. data/spec/stats/helpers_spec.rb +603 -0
  81. data/spec/validation/openssl_spec.rb +37 -0
  82. data/spec/validation/ssh_spec.rb +39 -0
  83. metadata +218 -19
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ # NOTE: this spec does not get defined or executed unless SystemLogger
4
+ # is defined, which only happens if Ruby standard-library 'syslog' module is
5
+ # available at runtime. Thus, the spec does not run on Windows.
6
+ #
7
+ # The selective execution is accomplished with a trailing 'if' for this
8
+ # outermost describe block. Crude but effective. You have been warned!
9
+ describe 'RightSupport::Log::SystemLogger' do
10
+ subject { RightSupport::Log::SystemLogger }
11
+
12
+ # Duplicate constants for easier reading
13
+ SEVERITY_MAP = RightSupport::Log::SystemLogger::SEVERITY_MAP
14
+ FACILITY_MAP = RightSupport::Log::SystemLogger::FACILITY_MAP
15
+
16
+ before(:each) do
17
+ @mock_syslog = flexmock(Syslog)
18
+ #indicates we sent a bogus severity to syslog!
19
+ @mock_syslog.should_receive(SEVERITY_MAP[Logger::UNKNOWN]).never
20
+ flexmock(Syslog).should_receive(:open).and_return(@mock_syslog).by_default
21
+ end
22
+
23
+ after(:each) do
24
+ subject.instance_eval { class_variable_set(:@@syslog, nil) }
25
+ end
26
+
27
+ context :initialize do
28
+ context 'with :facility option' do
29
+ FACILITY_MAP.each_pair do |name, const|
30
+ it "should handle #{name}" do
31
+ @mock_syslog.should_receive(:open).with('unit tests', nil, const)
32
+ subject.new('unit tests', :facility=>name)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ context :add do
39
+ context 'severity levels' do
40
+ levels = { :debug=>:debug,
41
+ :info=>:info, :warn=>:notice,
42
+ :error=>:warning, :fatal=>:err }
43
+
44
+ levels.each_pair do |logger_method, syslog_method|
45
+ it "translates Logger##{logger_method} to Syslog##{syslog_method}" do
46
+ @logger = subject.new('spec')
47
+ @mock_syslog.should_receive(syslog_method).with('moo bah oink')
48
+ @logger.__send__(logger_method, 'moo bah oink')
49
+ end
50
+ end
51
+ end
52
+
53
+ it 'escapes % characters to avoid confusing printf()' do
54
+ @logger = subject.new('spec')
55
+ flexmock(@logger).should_receive(:emit_syslog).with(Integer, 'All systems 100%% -- %%licious!')
56
+
57
+ @logger.info('All systems 100% -- %licious!')
58
+ end
59
+
60
+ context 'given :split option' do
61
+ it 'when true, splits multi-line messages' do
62
+ @logger = subject.new('spec', :split=>true)
63
+ flexmock(@logger).should_receive(:emit_syslog).times(5)
64
+
65
+ @logger.info("This is a\nmulti line\r\nlog message\n\rwith all kinds\n\n\rof stuff")
66
+ end
67
+
68
+ it 'when false, passes through multi-line messages' do
69
+ @logger = subject.new('spec', :split=>false)
70
+ flexmock(@logger).should_receive(:emit_syslog).times(1)
71
+
72
+ @logger.info("This is a\nmulti line\r\nlog message\n\rwith all kinds\n\n\rof stuff")
73
+ end
74
+ end
75
+
76
+ context 'given :color option' do
77
+ it 'when true, passes through ANSI color codes' do
78
+ @logger = subject.new('spec', :color=>true)
79
+ flexmock(@logger).should_receive(:emit_syslog).with(Integer, /[\e]/)
80
+
81
+ @logger.info("This has \e[16;32mcolor\e[7;0m inside it!")
82
+ end
83
+
84
+ it 'when false, strips out ANSI color codes' do
85
+ @logger = subject.new('spec', :color=>false)
86
+ flexmock(@logger).should_receive(:emit_syslog).with(Integer, /[^\e]/)
87
+
88
+ @logger.info("This has \e[16;32mcolor\e[7;0m inside it!")
89
+ end
90
+ end
91
+ end
92
+ end if defined?(RightSupport::Log::SystemLogger)
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Net::AddressHelper do
4
+ class AddressHelperTarget
5
+ include RightSupport::Net::AddressHelper
6
+ end
7
+
8
+ before(:each) do
9
+ @helper = AddressHelperTarget.new
10
+ end
11
+
12
+ describe :my_ipv4_addresses do
13
+
14
+ end
15
+
16
+ describe :my_ipv4_address do
17
+ it 'consistently chooses the lowest-numbered address' do
18
+ p = ['10.0.1.17', '192.168.7.5', '192.168.4.2', '127.0.0.1']
19
+ flexmock(@helper).should_receive(:local_routable_address).with(Socket::AF_INET).and_return(p[0])
20
+ flexmock(@helper).should_receive(:local_hostname_addresses).with(Socket::AF_INET).once.ordered.and_return([ p[3], p[2] ])
21
+ flexmock(@helper).should_receive(:local_hostname_addresses).with(Socket::AF_INET).once.ordered.and_return([ p[1], p[2] ])
22
+
23
+ one = @helper.my_ipv4_address(:private)
24
+ two = @helper.my_ipv4_address(:private)
25
+ one.should == p[0]
26
+ two.should == p[0]
27
+ end
28
+ end
29
+
30
+ describe :my_ipv4_addresses do
31
+ before(:each) do
32
+ routable_addr = '67.2.204.5'
33
+ hostname_addrs = ['127.0.0.1', '10.0.0.15', '67.2.204.5']
34
+ flexmock(@helper).should_receive(:local_routable_address).with(Socket::AF_INET).and_return(routable_addr)
35
+ flexmock(@helper).should_receive(:local_hostname_addresses).with(Socket::AF_INET).and_return(hostname_addrs)
36
+ flexmock(@helper).should_receive(:local_hostname_addresses).with(Socket::AF_INET).and_return(hostname_addrs)
37
+ end
38
+
39
+ context 'with :loopback option' do
40
+ it 'returns only loopback addresses' do
41
+ @helper.my_ipv4_addresses(:loopback).should == ['127.0.0.1']
42
+ end
43
+ end
44
+
45
+ context 'with :private option' do
46
+ it 'returns only private addresses' do
47
+ @helper.my_ipv4_addresses(:private).should == ['10.0.0.15']
48
+ end
49
+ end
50
+
51
+ context 'with :public option' do
52
+ it 'returns only public addresses' do
53
+ @helper.my_ipv4_addresses(:public).should == ['67.2.204.5']
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,382 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Net::LB::HealthCheck do
4
+ context :initialize do
5
+
6
+ end
7
+
8
+ before(:each) do
9
+ @endpoints = [1,2,3,4,5]
10
+ @yellow_states = 4
11
+ @reset_time = 60
12
+ @policy = RightSupport::Net::LB::HealthCheck.new(
13
+ :yellow_states => @yellow_states,
14
+ :reset_time => @reset_time)
15
+ @policy.set_endpoints(@endpoints)
16
+ @trials = 2500
17
+ end
18
+
19
+ context :initialize do
20
+ it 'starts endpoints in yellow-1 state' do
21
+ stats = @policy.get_stats
22
+ @endpoints.each { |ep| stats[ep].should == 'yellow-1' }
23
+ end
24
+ end
25
+
26
+ context :good do
27
+ context 'given a red server' do
28
+ before(:each) do
29
+ @red = @endpoints.first
30
+ @yellow_states.times { @policy.bad(@red, 0, Time.now) }
31
+ @policy.should have_red_endpoint(@red)
32
+ end
33
+
34
+ it "changes to yellow-N" do
35
+ @policy.good(@red, 0, Time.now)
36
+ @policy.should have_yellow_endpoint(@red, @yellow_states-1)
37
+ end
38
+ end
39
+
40
+ context 'given a yellow-N server' do
41
+ before(:each) do
42
+ @yellow = @endpoints.first
43
+ (@yellow_states-1).times { @policy.bad(@yellow, 0, Time.now) }
44
+ @policy.should have_yellow_endpoint(@yellow, @yellow_states)
45
+ end
46
+
47
+ it 'decreases the yellow level to N-1' do
48
+ @policy.good(@yellow, 0, Time.now)
49
+ @policy.should have_yellow_endpoint(@yellow, @yellow_states-1)
50
+ end
51
+
52
+ context 'when N == 1' do
53
+ before(:each) do
54
+ @yellow = @endpoints[1] #this should be yellow-1 since we haven't tampered with it yet
55
+ @policy.get_stats[@yellow].should == 'yellow-1'
56
+ end
57
+
58
+ it 'changes to green' do
59
+ @policy.should have_yellow_endpoint(@yellow, 1)
60
+ @policy.good(@yellow, 0, Time.now)
61
+ @policy.should have_green_endpoint(@yellow)
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'when on_health_change callback is enabled' do
67
+ before(:each) do
68
+ @yellow_states = 3
69
+ @health_updates = []
70
+ @policy = RightSupport::Net::LB::HealthCheck.new({
71
+ :yellow_states => @yellow_states, :reset_time => @reset_time,
72
+ :on_health_change => lambda { |health| @health_updates << health }})
73
+ @policy.set_endpoints(@endpoints)
74
+
75
+ # put everyone into green state, then forget all health updates. this helps us write an
76
+ # easier test.
77
+ @endpoints.each { |ep| @policy.good(ep, 0, Time.now) }
78
+ @health_updates = []
79
+ end
80
+
81
+ it "notifies of overall improving health only at transition points" do
82
+ endpoints = @endpoints.shuffle
83
+ endpoints.shuffle.each { |ep| @policy.bad(ep, 0, Time.now) }
84
+ endpoints.shuffle.each { |ep| @policy.bad(ep, 0, Time.now) }
85
+ endpoints.shuffle.each { |ep| @policy.bad(ep, 0, Time.now) }
86
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red']
87
+ @policy.good(endpoints[0], 0, Time.now)
88
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2']
89
+ endpoints[1..-1].each { |ep| @policy.good(ep, 0, Time.now) }
90
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2']
91
+ @policy.good(endpoints[0], 0, Time.now)
92
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2', 'yellow-1']
93
+ @policy.bad(endpoints[0], 0, Time.now)
94
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2', 'yellow-1', 'yellow-2']
95
+ @policy.good(endpoints[0], 0, Time.now)
96
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2', 'yellow-1', 'yellow-2', 'yellow-1']
97
+ 2.times { @policy.good(endpoints[1], 0, Time.now) }
98
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2', 'yellow-1', 'yellow-2', 'yellow-1', 'green']
99
+ endpoints.each { |ep| @policy.good(ep, 0, Time.now) }
100
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2', 'yellow-1', 'yellow-2', 'yellow-1', 'green']
101
+ endpoints.each { |ep| @policy.good(ep, 0, Time.now) }
102
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red', 'yellow-2', 'yellow-1', 'yellow-2', 'yellow-1', 'green']
103
+ end
104
+ end
105
+ end
106
+
107
+ context :bad do
108
+ context 'given a green server' do
109
+ before(:each) do
110
+ @green = @endpoints.first
111
+ @policy.good(@green, 0, Time.now)
112
+ @policy.should have_green_endpoint(@green)
113
+ end
114
+
115
+ it 'changes to yellow-1' do
116
+ @policy.bad(@green, 0, Time.now)
117
+ @policy.should have_yellow_endpoint(@green, 1)
118
+ end
119
+ end
120
+
121
+ context 'given a yellow-N server' do
122
+ before(:each) do
123
+ @yellow = @endpoints.first
124
+ end
125
+
126
+ it 'increases the yellow level to N+1' do
127
+ @policy.bad(@yellow, 0, Time.now)
128
+ @policy.should have_yellow_endpoint(@yellow, 2)
129
+ end
130
+
131
+ context 'when N == yellow_states-1' do
132
+ before(:each) do
133
+ n = @yellow_states - 2
134
+ n.times { @policy.bad(@yellow, 0, Time.now) }
135
+ @policy.should have_yellow_endpoint(@yellow, n+1)
136
+ end
137
+
138
+ it 'changes to red' do
139
+ @policy.bad(@yellow, 0, Time.now)
140
+ @policy.should have_red_endpoint(@yellow)
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'given a red server' do
146
+ it 'does nothing' do
147
+ @red = @endpoints.first
148
+ @yellow_states.times { @policy.bad(@red, 0, Time.now) }
149
+ @policy.should have_red_endpoint(@red)
150
+
151
+ @policy.bad(@red, 0, Time.now)
152
+ @policy.should have_red_endpoint(@red)
153
+ end
154
+ end
155
+
156
+ context 'when on_health_change callback is enabled' do
157
+ before(:each) do
158
+ @yellow_states = 3
159
+ @health_updates = []
160
+ @policy = RightSupport::Net::LB::HealthCheck.new({
161
+ :yellow_states => @yellow_states, :reset_time => @reset_time,
162
+ :on_health_change => lambda { |health| @health_updates << health }})
163
+ @policy.set_endpoints(@endpoints)
164
+
165
+ # put everyone into green state, then forget all health updates. this helps us write an
166
+ # easier test.
167
+ @endpoints.each { |ep| @policy.good(ep, 0, Time.now) }
168
+ @health_updates = []
169
+ end
170
+
171
+ it "notifies of overall worsening health only at transition points" do
172
+ # make most endpoints yellow-1
173
+ endpoints = @endpoints.shuffle
174
+ endpoints[1..-1].each { |ep| @policy.bad(ep, 0, Time.now) }
175
+ @policy.bad(endpoints[0], 0, Time.now)
176
+ @health_updates.should == ['yellow-1']
177
+ endpoints.each { |ep| @policy.bad(ep, 0, Time.now) }
178
+ @health_updates.should == ['yellow-1', 'yellow-2']
179
+ endpoints[1..-1].each { |ep| @policy.bad(ep, 0, Time.now) }
180
+ @health_updates.should == ['yellow-1', 'yellow-2']
181
+ @policy.bad(endpoints[0], 0, Time.now)
182
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red']
183
+ @policy.bad(endpoints[0], 0, Time.now)
184
+ @health_updates.should == ['yellow-1', 'yellow-2', 'red']
185
+ end
186
+ end
187
+ end
188
+
189
+ context :next do
190
+
191
+ context 'given all green servers' do
192
+ it 'chooses fairly' do
193
+ test_random_distribution do
194
+ @policy.next
195
+ end
196
+ end
197
+ end
198
+
199
+ context 'given all red servers' do
200
+ it 'returns nil to indicate no servers are available' do
201
+ @endpoints.each do |endpoint|
202
+ @yellow_states.times { @policy.bad(endpoint, 0, Time.now) }
203
+ @policy.should have_red_endpoint(endpoint)
204
+ end
205
+ @policy.next.should be_nil
206
+ end
207
+ end
208
+
209
+ context 'given a mixture of servers' do
210
+ it 'never chooses red servers' do
211
+ @red = @endpoints.first
212
+ @yellow_states.times { @policy.bad(@red, 0, Time.now) }
213
+ @policy.should have_red_endpoint(@red)
214
+
215
+ seen = find_empirical_distribution(@trials,@endpoints) do
216
+ @policy.next
217
+ end
218
+
219
+ seen.include?(@red).should be_false
220
+ end
221
+
222
+ it 'chooses fairly from the green and yellow servers' do
223
+ @red = @endpoints.first
224
+ @yellow_states.times { @policy.bad(@red, 0, Time.now) }
225
+ @policy.should have_red_endpoint(@red)
226
+
227
+ seen = find_empirical_distribution(@trials,@endpoints) do
228
+ @policy.next
229
+ end
230
+
231
+ seen.include?(@red).should be_false
232
+ should_be_chosen_fairly(seen, @trials, @endpoints.size - 1)
233
+ end
234
+
235
+ it 'demands a health check for yellow servers' do
236
+ pending
237
+ end
238
+
239
+ it "maintains the same order of green and yellow servers" do
240
+ actual = []
241
+ expected = [3,4,1,2]
242
+
243
+ endpoints = [1,2,3,4]
244
+ policy = RightSupport::Net::LB::HealthCheck.new(:yellow_states => 1)
245
+ policy.set_endpoints(endpoints)
246
+ policy.instance_variable_set(:@counter, 1)
247
+ endpoints.size.times do
248
+ endpoint, yellow = policy.next
249
+ policy.bad(endpoint, 0, Time.now) unless endpoint == 4
250
+ actual << endpoint
251
+ end
252
+
253
+ actual.should == expected
254
+ end
255
+ end
256
+
257
+ context 'given a red server' do
258
+ before(:each) do
259
+ @red = @endpoints.first
260
+ (@yellow_states-1).times { @policy.bad(@red, 0, Time.now - 60) }
261
+ end
262
+
263
+ context 'when @reset_time passes' do
264
+ it 'resets the server to yellow' do
265
+ @policy.should have_red_endpoint(@red)
266
+ @policy.next
267
+ @policy.should have_yellow_endpoint(@red, @yellow_states-1)
268
+ end
269
+ end
270
+
271
+ end
272
+
273
+ context 'given a yellow-2 server' do
274
+ before(:each) do
275
+ @yellow = @endpoints.first
276
+ @policy.bad(@yellow, 0, Time.now - 60)
277
+ @policy.should have_yellow_endpoint(@yellow, 2)
278
+ end
279
+
280
+ context 'when @reset_time passes' do
281
+ it 'decreases the yellow level to N-1' do
282
+ @policy.next
283
+ @policy.should have_yellow_endpoint(@yellow, 1)
284
+ end
285
+ end
286
+ end
287
+
288
+ context 'given a yellow-1 server' do
289
+ before(:each) do
290
+ @yellow = @endpoints.first
291
+ @policy.good(@yellow, 0, Time.now - 60)
292
+ @policy.bad(@yellow, 0, Time.now - 60)
293
+ @policy.should have_yellow_endpoint(@yellow, 1)
294
+ end
295
+
296
+ context 'when @reset_time passes' do
297
+ it 'resets the server to green' do
298
+ @policy.next
299
+ @policy.should have_green_endpoint(@yellow)
300
+ end
301
+ end
302
+ end
303
+ end
304
+
305
+ context :get_stats do
306
+ context 'given all green servers' do
307
+ before(:each) do
308
+ @endpoints.each { |ep| @policy.good(ep, 0, Time.now) }
309
+ end
310
+
311
+ it 'reports all endpoints as green' do
312
+ expected_stats = {}
313
+ @endpoints.each { |ep| expected_stats[ep] = 'green' }
314
+
315
+ @policy.get_stats.should_not be_nil
316
+ @policy.get_stats.should == expected_stats
317
+ end
318
+ end
319
+
320
+ context 'given all red servers' do
321
+ it 'reports all endpoints as red' do
322
+ expected_stats = {}
323
+ @endpoints.each { |ep| expected_stats[ep] = 'red' }
324
+
325
+ @endpoints.each do |endpoint|
326
+ @yellow_states.times { @policy.bad(endpoint, 0, Time.now) }
327
+ end
328
+
329
+ @policy.get_stats.should_not be_nil
330
+ @policy.get_stats.should == expected_stats
331
+ end
332
+ end
333
+
334
+ context 'given all yellow-N servers' do
335
+ it 'reports all endpoints as yellow-N' do
336
+ expected_stats = {}
337
+ @endpoints.each { |ep| expected_stats[ep] = "yellow-#{@yellow_states - 1}" }
338
+
339
+ @endpoints.each do |endpoint|
340
+ @yellow_states.times { @policy.bad(endpoint, 0, Time.now) }
341
+ @policy.good(endpoint, 0, Time.now)
342
+ end
343
+
344
+ @policy.get_stats.should_not be_nil
345
+ @policy.get_stats.should == expected_stats
346
+ end
347
+ end
348
+ end
349
+
350
+ context :set_endpoints do
351
+ context 'given endpoints stack does not exist' do
352
+ before(:each) do
353
+ @policy = RightSupport::Net::LB::HealthCheck.new({
354
+ :yellow_states => @yellow_states, :reset_time => @reset_time})
355
+ end
356
+
357
+ it 'acts as initializer' do
358
+ @policy.set_endpoints(@endpoints)
359
+ @endpoints.include?(@policy.next.first).should be_true
360
+ end
361
+ end
362
+
363
+ context 'given an existing endpoints stack' do
364
+ it 'updates composition and saves previous statuses of endpoints' do
365
+ expected_stats = {}
366
+ @endpoints.each { |ep| expected_stats[ep] = "yellow-#{@yellow_states - 1}" }
367
+
368
+ @endpoints.each do |endpoint|
369
+ @yellow_states.times { @policy.bad(endpoint, 0, Time.now) }
370
+ @policy.good(endpoint, 0, Time.now)
371
+ end
372
+
373
+ @new_endpoins = [6,7]
374
+ @new_endpoins.each { |ep| expected_stats[ep] = "yellow-1" }
375
+
376
+ @updated_endpoints = @endpoints + @new_endpoins
377
+ @policy.set_endpoints(@updated_endpoints)
378
+ @policy.get_stats.should eql(expected_stats)
379
+ end
380
+ end
381
+ end #:set_endpoints
382
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Net::LB::RoundRobin do
4
+ before(:each) do
5
+ @endpoints = [1,2,3,4,5]
6
+ @policy = RightSupport::Net::LB::RoundRobin.new()
7
+ @policy.set_endpoints(@endpoints)
8
+ end
9
+
10
+ it 'chooses fairly' do
11
+ test_random_distribution do
12
+ @policy.next
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ describe RightSupport::Net::LB::Sticky do
4
+
5
+ before(:all) do
6
+ @endpoints = [1,2,3,4,5]
7
+ @trials = 2500
8
+ end
9
+
10
+ before(:each) do
11
+ @policy = RightSupport::Net::LB::Sticky.new({})
12
+ end
13
+
14
+ context :initialize do
15
+ it 'creates policy with an empty list of endpoints' do
16
+ @policy.next.should be_nil
17
+ end
18
+ end
19
+
20
+ context :next do
21
+
22
+ before(:each) do
23
+ @policy.set_endpoints(@endpoints)
24
+ end
25
+
26
+ context 'given all servers are healthy' do
27
+ it 'sticks to chosen one' do
28
+ chance = 1.0
29
+ seen = find_empirical_distribution(@trials,@endpoints) do
30
+ @policy.next
31
+ end
32
+ seen.each_pair do |_, count|
33
+ (Float(count) / Float(@trials)).should be_within(0.025).of(chance) #allow 5% margin of error
34
+ end
35
+ end
36
+ end
37
+
38
+ context 'given a chosen server becomes unavailable' do
39
+ it 'chooses the next server and sticks to it' do
40
+ @ep1 = @policy.next.first
41
+
42
+ seen = find_empirical_distribution(@trials,@endpoints) do
43
+ @policy.next
44
+ end
45
+ seen[[@ep1,false]].should eql(@trials)
46
+
47
+ @policy.bad(@chosen,0,0)
48
+ @ep2 = @policy.next.first
49
+
50
+ seen = find_empirical_distribution(@trials,@endpoints) do
51
+ @policy.next
52
+ end
53
+ seen[[@ep1,false]].should be_nil
54
+ seen[[@ep2,false]].should eql(@trials)
55
+ end
56
+ end
57
+ end
58
+
59
+ context :set_endpoints do
60
+ context 'given an empty list of endpoints' do
61
+ it 'acts as initializer' do
62
+ @policy.next.should be_nil
63
+ @policy.set_endpoints(@endpoints)
64
+ @endpoints.include?(@policy.next.first).should be_true
65
+ end
66
+ end
67
+
68
+ context 'given an existing list endpoints' do
69
+ before(:each) do
70
+ @policy.set_endpoints(@endpoints)
71
+ end
72
+
73
+ context 'and updated list of endpoints contains a chosen server' do
74
+ it 'updates composition, but still using chosen server' do
75
+ @chosen_endpoint = @policy.next.first
76
+ @updated_endpoints = @endpoints + [6,7]
77
+ @policy.set_endpoints(@updated_endpoints)
78
+ @policy.next.first.should be_eql(@chosen_endpoint)
79
+ end
80
+ end
81
+
82
+ context 'and updated list of endpoints does not contain a chosen server' do
83
+ it 'updates composition and chooses new server' do
84
+ @chosen_endpoint = @policy.next.first
85
+ @updated_endpoints = @endpoints - [@chosen_endpoint]
86
+ @policy.set_endpoints(@updated_endpoints)
87
+ @policy.next.first.should_not be_eql(@chosen_endpoint)
88
+ end
89
+ end
90
+ end
91
+ end #:set_endpoints
92
+ end