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.
- data/.rspec +4 -0
- data/CHANGELOG.rdoc +37 -0
- data/Gemfile +29 -0
- data/Gemfile.lock +111 -0
- data/README.rdoc +2 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/features/balancer_error_handling.feature +34 -0
- data/features/balancer_health_check.feature +33 -0
- data/features/continuous_integration.feature +51 -0
- data/features/continuous_integration_cucumber.feature +28 -0
- data/features/continuous_integration_rspec1.feature +28 -0
- data/features/continuous_integration_rspec2.feature +28 -0
- data/features/http_client_timeout.feature +19 -0
- data/features/serialization.feature +95 -0
- data/features/step_definitions/http_client_steps.rb +27 -0
- data/features/step_definitions/request_balancer_steps.rb +93 -0
- data/features/step_definitions/ruby_steps.rb +176 -0
- data/features/step_definitions/serialization_steps.rb +96 -0
- data/features/step_definitions/server_steps.rb +134 -0
- data/features/support/env.rb +138 -0
- data/features/support/file_utils_bundler_mixin.rb +45 -0
- data/lib/right_support/ci/java_cucumber_formatter.rb +22 -8
- data/lib/right_support/ci/java_spec_formatter.rb +26 -8
- data/lib/right_support/ci/rake_task.rb +3 -0
- data/lib/right_support/ci.rb +24 -0
- data/lib/right_support/crypto/signed_hash.rb +22 -0
- data/lib/right_support/data/serializer.rb +24 -2
- data/lib/right_support/net/address_helper.rb +20 -8
- data/lib/right_support/net/dns.rb +20 -8
- data/lib/right_support/net/http_client.rb +22 -0
- data/lib/right_support/net/request_balancer.rb +27 -21
- data/lib/right_support/net/s3_helper.rb +20 -8
- data/lib/right_support/net/ssl/open_ssl_patch.rb +22 -0
- data/lib/right_support/net/ssl.rb +20 -8
- data/lib/right_support/ruby/easy_singleton.rb +22 -0
- data/lib/right_support/ruby/object_extensions.rb +22 -0
- data/lib/right_support/ruby/string_extensions.rb +1 -1
- data/lib/right_support.rb +13 -10
- data/right_support.gemspec +180 -18
- data/right_support.rconf +8 -0
- data/spec/config/feature_set_spec.rb +83 -0
- data/spec/crypto/signed_hash_spec.rb +60 -0
- data/spec/data/hash_tools_spec.rb +471 -0
- data/spec/data/uuid_spec.rb +45 -0
- data/spec/db/cassandra_model_part1_spec.rb +84 -0
- data/spec/db/cassandra_model_part2_spec.rb +73 -0
- data/spec/db/cassandra_model_spec.rb +359 -0
- data/spec/fixtures/encrypted_priv_rsa.pem +30 -0
- data/spec/fixtures/good_priv_dsa.pem +12 -0
- data/spec/fixtures/good_priv_rsa.pem +15 -0
- data/spec/fixtures/good_pub_dsa.ssh +1 -0
- data/spec/fixtures/good_pub_rsa.pem +5 -0
- data/spec/fixtures/good_pub_rsa.ssh +1 -0
- data/spec/log/exception_logger_spec.rb +76 -0
- data/spec/log/filter_logger_spec.rb +8 -0
- data/spec/log/mixin_spec.rb +62 -0
- data/spec/log/multiplexer_spec.rb +54 -0
- data/spec/log/null_logger_spec.rb +36 -0
- data/spec/log/system_logger_spec.rb +92 -0
- data/spec/net/address_helper_spec.rb +57 -0
- data/spec/net/balancing/health_check_spec.rb +382 -0
- data/spec/net/balancing/round_robin_spec.rb +15 -0
- data/spec/net/balancing/sticky_policy_spec.rb +92 -0
- data/spec/net/dns_spec.rb +152 -0
- data/spec/net/http_client_spec.rb +171 -0
- data/spec/net/request_balancer_spec.rb +579 -0
- data/spec/net/s3_helper_spec.rb +160 -0
- data/spec/net/ssl_spec.rb +42 -0
- data/spec/net/string_encoder_spec.rb +58 -0
- data/spec/rack/log_setter_spec.rb +5 -0
- data/spec/rack/request_logger_spec.rb +68 -0
- data/spec/rack/request_tracker_spec.rb +5 -0
- data/spec/ruby/easy_singleton_spec.rb +72 -0
- data/spec/ruby/object_extensions_spec.rb +27 -0
- data/spec/ruby/string_extensions_spec.rb +98 -0
- data/spec/spec_helper.rb +181 -0
- data/spec/stats/activity_spec.rb +193 -0
- data/spec/stats/exceptions_spec.rb +123 -0
- data/spec/stats/helpers_spec.rb +603 -0
- data/spec/validation/openssl_spec.rb +37 -0
- data/spec/validation/ssh_spec.rb +39 -0
- metadata +218 -19
@@ -0,0 +1,579 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestException < Exception; end
|
4
|
+
class OtherTestException < Exception; end
|
5
|
+
class BigDeal < TestException; end
|
6
|
+
class NoBigDeal < TestException; end
|
7
|
+
|
8
|
+
class MockHttpError < Exception
|
9
|
+
attr_reader :http_code
|
10
|
+
def initialize(message=nil, code=400)
|
11
|
+
super(message)
|
12
|
+
@http_code = code
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MockResourceNotFound < MockHttpError
|
17
|
+
def initialize(message=nil)
|
18
|
+
super(message, 404)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MockRequestTimeout < MockHttpError
|
23
|
+
def initialize(message=nil)
|
24
|
+
super(message, 408)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe RightSupport::Net::RequestBalancer do
|
29
|
+
def test_raise(fatal, do_raise, expect)
|
30
|
+
bases = []
|
31
|
+
base = do_raise.superclass
|
32
|
+
while base != Exception
|
33
|
+
bases << base
|
34
|
+
base = base.superclass
|
35
|
+
end
|
36
|
+
|
37
|
+
exception = expect.first
|
38
|
+
count = expect.last
|
39
|
+
rb = RightSupport::Net::RequestBalancer.new([1,2,3], :fatal=>fatal)
|
40
|
+
@tries = 0
|
41
|
+
|
42
|
+
code = lambda do
|
43
|
+
rb.request do |_|
|
44
|
+
@tries += 1
|
45
|
+
next unless do_raise
|
46
|
+
if bases.include?(RestClient::ExceptionWithResponse)
|
47
|
+
#Special case: RestClient exceptions need an HTTP response, but they
|
48
|
+
#have stack recursion if we give them something other than a real
|
49
|
+
#HTTP response. Blech!
|
50
|
+
raise do_raise, nil
|
51
|
+
else
|
52
|
+
#Generic exception with message
|
53
|
+
raise do_raise, 'Bah humbug; fie on thee!'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if exception
|
59
|
+
code.should raise_error(expect[0])
|
60
|
+
else
|
61
|
+
code.should_not raise_error
|
62
|
+
end
|
63
|
+
|
64
|
+
@tries.should == count
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_bad_endpoint_requests(number_of_endpoints)
|
68
|
+
test = Proc.new do |endpoint|
|
69
|
+
@health_checks += 1
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
expect = number_of_endpoints
|
74
|
+
yellow_states = 4
|
75
|
+
rb = RightSupport::Net::RequestBalancer.new((1..expect).to_a,
|
76
|
+
:policy => RightSupport::Net::LB::HealthCheck,
|
77
|
+
:health_check => test,
|
78
|
+
:yellow_states => yellow_states)
|
79
|
+
@health_checks = 0
|
80
|
+
tries = 0
|
81
|
+
l = lambda do
|
82
|
+
rb.request do |endpoint|
|
83
|
+
tries += 1
|
84
|
+
raise Exception
|
85
|
+
end
|
86
|
+
end
|
87
|
+
yellow_states.times do
|
88
|
+
l.should raise_error
|
89
|
+
end
|
90
|
+
tries.should == expect
|
91
|
+
@health_checks.should == expect * (yellow_states - 1)
|
92
|
+
end
|
93
|
+
|
94
|
+
context :initialize do
|
95
|
+
it 'requires a list of endpoint URLs' do
|
96
|
+
lambda do
|
97
|
+
RightSupport::Net::RequestBalancer.new(nil)
|
98
|
+
end.should raise_exception(ArgumentError)
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'with Integer :retry option' do
|
102
|
+
it 'stops after N total tries' do
|
103
|
+
lambda do
|
104
|
+
@tries = 0
|
105
|
+
RightSupport::Net::RequestBalancer.new([1, 2, 3], :retry=>1).request do |u|
|
106
|
+
@tries += 1
|
107
|
+
raise NoBigDeal
|
108
|
+
end
|
109
|
+
end.should raise_error
|
110
|
+
@tries.should == 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'with Proc :retry option' do
|
115
|
+
it 'stops when call evaluates to false' do
|
116
|
+
@tries = 0
|
117
|
+
|
118
|
+
proc = Proc.new do |ep, n|
|
119
|
+
@tries < 1
|
120
|
+
end
|
121
|
+
|
122
|
+
balancer = RightSupport::Net::RequestBalancer.new([1, 2, 3], :retry => proc)
|
123
|
+
lambda do
|
124
|
+
balancer.request do |u|
|
125
|
+
@tries += 1
|
126
|
+
raise NoBigDeal
|
127
|
+
end
|
128
|
+
end.should raise_error(RightSupport::Net::NoResult)
|
129
|
+
|
130
|
+
@tries.should == 1
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context ':fatal option' do
|
135
|
+
it 'has reasonable defaults' do
|
136
|
+
exceptions = RightSupport::Net::RequestBalancer::DEFAULT_FATAL_EXCEPTIONS - [SignalException]
|
137
|
+
balancer = RightSupport::Net::RequestBalancer.new([1])
|
138
|
+
exceptions.each do |klass|
|
139
|
+
lambda do
|
140
|
+
balancer.request { |ep| raise klass }
|
141
|
+
end.should raise_error(klass)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with a Proc' do
|
146
|
+
it 'validates the arity' do
|
147
|
+
bad_lambda = lambda { |too, many, arguments| }
|
148
|
+
lambda do
|
149
|
+
RightSupport::Net::RequestBalancer.new([1,2], :fatal=>bad_lambda)
|
150
|
+
end.should raise_error(ArgumentError)
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'delegates to the Proc' do
|
154
|
+
always_retry = lambda { |e| false }
|
155
|
+
balancer = RightSupport::Net::RequestBalancer.new([1,2], :fatal=>always_retry)
|
156
|
+
|
157
|
+
lambda do
|
158
|
+
balancer.request do |ep|
|
159
|
+
raise BigDeal
|
160
|
+
end
|
161
|
+
end.should raise_error(RightSupport::Net::NoResult)
|
162
|
+
|
163
|
+
lambda do
|
164
|
+
balancer.request do |ep|
|
165
|
+
raise ArgumentError
|
166
|
+
end
|
167
|
+
end.should raise_error(RightSupport::Net::NoResult)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'with an Exception' do
|
172
|
+
it 'considers that class of Exception to be fatal' do
|
173
|
+
balancer = RightSupport::Net::RequestBalancer.new([1], :fatal=>BigDeal)
|
174
|
+
lambda do
|
175
|
+
balancer.request { |ep| raise BigDeal }
|
176
|
+
end.should raise_error(BigDeal)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'with an Array' do
|
181
|
+
it 'considers any class in the array to be fatal' do
|
182
|
+
exceptions = [ArgumentError, BigDeal]
|
183
|
+
balancer = RightSupport::Net::RequestBalancer.new([1], :fatal=>exceptions)
|
184
|
+
exceptions.each do |klass|
|
185
|
+
lambda do
|
186
|
+
balancer.request { |ep| raise klass }
|
187
|
+
end.should raise_error(klass)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'with :on_exception option' do
|
194
|
+
it 'validates the arity' do
|
195
|
+
bad_lambda = lambda { |way, too, many, arguments| }
|
196
|
+
lambda do
|
197
|
+
RightSupport::Net::RequestBalancer.new([1,2], :on_exception=>bad_lambda)
|
198
|
+
end.should raise_error(ArgumentError)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context 'with :policy option' do
|
203
|
+
it 'accepts a Class' do
|
204
|
+
policy = RightSupport::Net::LB::RoundRobin
|
205
|
+
lambda {
|
206
|
+
RightSupport::Net::RequestBalancer.new([1,2], :policy=>policy)
|
207
|
+
}.should_not raise_error
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'accepts an object' do
|
211
|
+
policy = RightSupport::Net::LB::RoundRobin.new([1,2])
|
212
|
+
lambda {
|
213
|
+
RightSupport::Net::RequestBalancer.new([1,2], :policy=>policy)
|
214
|
+
}.should_not raise_error
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'checks for duck-type compatibility' do
|
218
|
+
lambda {
|
219
|
+
RightSupport::Net::RequestBalancer.new([1,2], :policy=>String)
|
220
|
+
}.should raise_error
|
221
|
+
lambda {
|
222
|
+
RightSupport::Net::RequestBalancer.new([1,2], :policy=>'I like cheese')
|
223
|
+
}.should raise_error
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'with :health_check option' do
|
228
|
+
before(:each) do
|
229
|
+
@health_check = Proc.new {|endpoint| "HealthCheck passed for #{endpoint}!" }
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'accepts a block' do
|
233
|
+
lambda {
|
234
|
+
RightSupport::Net::RequestBalancer.new([1,2], :health_check => @health_check)
|
235
|
+
}.should_not raise_error
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'calls specified block' do
|
239
|
+
@balancer = RightSupport::Net::RequestBalancer.new([1,2], :health_check => @health_check)
|
240
|
+
@options = @balancer.instance_variable_get("@options")
|
241
|
+
@options[:health_check].call(1).should be_eql("HealthCheck passed for 1!")
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'with default :health_check option' do
|
247
|
+
it 'calls default block' do
|
248
|
+
@balancer = RightSupport::Net::RequestBalancer.new([1,2])
|
249
|
+
@options = @balancer.instance_variable_get("@options")
|
250
|
+
@options[:health_check].call(1).should be_true
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'with :on_health_change option' do
|
255
|
+
before(:each) do
|
256
|
+
@health_updates = []
|
257
|
+
@on_health_change = Proc.new {|health| @health_updates << health }
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'accepts a block' do
|
261
|
+
lambda {
|
262
|
+
RightSupport::Net::RequestBalancer.new([1,2], :on_health_change => @on_health_change)
|
263
|
+
}.should_not raise_error
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'with :resolve option' do
|
268
|
+
before(:each) do
|
269
|
+
flexmock(RightSupport::Net::DNS).should_receive(:resolve_with_hostnames).
|
270
|
+
with(['host1', 'host2']).and_return({'host1' => ['1.1.1.1', '2.2.2.2'], 'host2' => ['3.3.3.3']})
|
271
|
+
@balancer = RightSupport::Net::RequestBalancer.new(['host1', 'host2'], :resolve => 15)
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'performs an initial resolution' do
|
275
|
+
@balancer.instance_variable_get("@resolved_at").to_f.should be_close(Time.now.to_f, 1.0)
|
276
|
+
@balancer.instance_variable_get("@ips").should include('1.1.1.1')
|
277
|
+
@balancer.instance_variable_get("@ips").should include('2.2.2.2')
|
278
|
+
@balancer.instance_variable_get("@ips").should include('3.3.3.3')
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context :request do
|
284
|
+
it 'requires a block' do
|
285
|
+
lambda do
|
286
|
+
RightSupport::Net::RequestBalancer.new([1]).request
|
287
|
+
end.should raise_exception(ArgumentError)
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'retries until a request completes' do
|
291
|
+
list = [1,2,3,4,5,6,7,8,9,10]
|
292
|
+
|
293
|
+
10.times do
|
294
|
+
x = RightSupport::Net::RequestBalancer.new(list).request do |l|
|
295
|
+
raise NoBigDeal, "Fall down go boom!" unless l == 5
|
296
|
+
l
|
297
|
+
end
|
298
|
+
|
299
|
+
x.should == 5
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'raises if no request completes' do
|
304
|
+
lambda do
|
305
|
+
RightSupport::Net::RequestBalancer.request([1,2,3]) do |l|
|
306
|
+
raise NoBigDeal, "Fall down go boom!"
|
307
|
+
end
|
308
|
+
end.should raise_exception(RightSupport::Net::NoResult, /NoBigDeal/)
|
309
|
+
end
|
310
|
+
|
311
|
+
context 'without :fatal option' do
|
312
|
+
it 're-raises reasonable default fatal errors' do
|
313
|
+
test_raise(nil, ArgumentError, [ArgumentError, 1])
|
314
|
+
test_raise(nil, MockResourceNotFound, [MockResourceNotFound, 1])
|
315
|
+
test_raise(nil, RSpec::Expectations::ExpectationNotMetError, [RSpec::Expectations::ExpectationNotMetError, 1])
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'swallows StandardError and friends' do
|
319
|
+
[SystemCallError, SocketError].each do |klass|
|
320
|
+
test_raise(nil, klass, [RightSupport::Net::NoResult, 3])
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'with :fatal option' do
|
326
|
+
it 're-raises fatal errors' do
|
327
|
+
test_raise(BigDeal, BigDeal, [BigDeal, 1])
|
328
|
+
test_raise([BigDeal, NoBigDeal], NoBigDeal, [NoBigDeal, 1])
|
329
|
+
test_raise(true, NoBigDeal, [NoBigDeal, 1])
|
330
|
+
test_raise(lambda {|e| e.is_a? BigDeal }, BigDeal, [BigDeal, 1])
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'swallows nonfatal errors' do
|
334
|
+
test_raise(nil, BigDeal, [RightSupport::Net::NoResult, 3])
|
335
|
+
test_raise(BigDeal, NoBigDeal, [RightSupport::Net::NoResult, 3])
|
336
|
+
test_raise([BigDeal], NoBigDeal, [RightSupport::Net::NoResult, 3])
|
337
|
+
test_raise(false, NoBigDeal, [RightSupport::Net::NoResult, 3])
|
338
|
+
test_raise(lambda {|e| e.is_a? BigDeal }, NoBigDeal, [RightSupport::Net::NoResult, 3])
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
context 'with default :fatal option' do
|
343
|
+
it 'retries most Ruby builtin errors' do
|
344
|
+
list = [1,2,3,4,5,6,7,8,9,10]
|
345
|
+
rb = RightSupport::Net::RequestBalancer.new(list)
|
346
|
+
|
347
|
+
[IOError, SystemCallError, SocketError].each do |klass|
|
348
|
+
test_raise(nil, klass, [RightSupport::Net::NoResult, 3])
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'does not retry program errors' do
|
353
|
+
list = [1,2,3,4,5,6,7,8,9,10]
|
354
|
+
rb = RightSupport::Net::RequestBalancer.new(list)
|
355
|
+
|
356
|
+
[ArgumentError, LoadError, NameError].each do |klass|
|
357
|
+
test_raise(nil, klass, [klass, 1])
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'retries HTTP timeouts' do
|
362
|
+
test_raise(nil, MockRequestTimeout, [RightSupport::Net::NoResult, 3])
|
363
|
+
test_raise(nil, RestClient::RequestTimeout, [RightSupport::Net::NoResult, 3])
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'does not retry HTTP 4xx other than timeout' do
|
367
|
+
list = [1,2,3,4,5,6,7,8,9,10]
|
368
|
+
rb = RightSupport::Net::RequestBalancer.new(list)
|
369
|
+
|
370
|
+
codes = [401, 402, 403, 404, 405, 406, 407, 409]
|
371
|
+
codes.each do |code|
|
372
|
+
lambda do
|
373
|
+
rb.request { |l| raise MockHttpError.new(nil, code) }
|
374
|
+
end.should raise_error(MockHttpError)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
context 'with default :retry option' do
|
379
|
+
it 'marks endpoints as bad if they encounter retryable errors' do
|
380
|
+
rb = RightSupport::Net::RequestBalancer.new([1,2,3], :policy => RightSupport::Net::LB::HealthCheck, :health_check => Proc.new {|endpoint| false})
|
381
|
+
expect = rb.get_stats
|
382
|
+
codes = [401, 402, 403, 404, 405, 406, 407, 408, 409]
|
383
|
+
codes.each do |code|
|
384
|
+
lambda do
|
385
|
+
rb.request { |l| raise MockHttpError.new(nil, code) }
|
386
|
+
end.should raise_error
|
387
|
+
end
|
388
|
+
|
389
|
+
rb.get_stats.should_not == expect
|
390
|
+
end
|
391
|
+
|
392
|
+
it 'does not mark endpoints as bad if they raise fatal errors' do
|
393
|
+
rb = RightSupport::Net::RequestBalancer.new([1,2,3], :policy => RightSupport::Net::LB::HealthCheck, :health_check => Proc.new {|endpoint| false})
|
394
|
+
codes = [401, 402, 403, 404, 405, 406, 407, 409]
|
395
|
+
codes.each do |code|
|
396
|
+
lambda do
|
397
|
+
rb.request { |l| raise MockHttpError.new(nil, code) }
|
398
|
+
end.should raise_error
|
399
|
+
end
|
400
|
+
|
401
|
+
# The EPs started in yellow-1, then passed an initial health check which
|
402
|
+
# changed them to green, then executed a failing request, which should
|
403
|
+
# not count against them. They should still be green.
|
404
|
+
rb.get_stats.should == {1=>'green', 2=>'green', 3=>'green'}
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
context 'with :on_exception option' do
|
410
|
+
before(:each) do
|
411
|
+
@list = [1,2,3,4,5,6,7,8,9,10]
|
412
|
+
@callback = flexmock('Callback proc')
|
413
|
+
@callback.should_receive(:respond_to?).with(:call).and_return(true)
|
414
|
+
@callback.should_receive(:respond_to?).with(:arity).and_return(true)
|
415
|
+
@callback.should_receive(:arity).and_return(3)
|
416
|
+
@rb = RightSupport::Net::RequestBalancer.new(@list, :fatal=>BigDeal, :on_exception=>@callback)
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'calls me back with fatal exceptions' do
|
420
|
+
@callback.should_receive(:call).with(true, BigDeal, Integer)
|
421
|
+
lambda {
|
422
|
+
@rb.request { raise BigDeal }
|
423
|
+
}.should raise_error(BigDeal)
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'calls me back with nonfatal exceptions' do
|
427
|
+
@callback.should_receive(:call).with(false, NoBigDeal, Integer)
|
428
|
+
lambda {
|
429
|
+
@rb.request { raise NoBigDeal }
|
430
|
+
}.should raise_error(RightSupport::Net::NoResult)
|
431
|
+
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
context 'given a class-level logger' do
|
436
|
+
before(:all) do
|
437
|
+
@logger = Logger.new(StringIO.new)
|
438
|
+
RightSupport::Net::RequestBalancer.logger = @logger
|
439
|
+
RightSupport::Net::LB::HealthCheck.logger = @logger
|
440
|
+
end
|
441
|
+
|
442
|
+
after(:all) do
|
443
|
+
RightSupport::Net::RequestBalancer.logger = nil
|
444
|
+
end
|
445
|
+
|
446
|
+
context 'when a retryable exception is raised' do
|
447
|
+
it 'logs an error' do
|
448
|
+
flexmock(@logger).should_receive(:error).times(4)
|
449
|
+
|
450
|
+
lambda {
|
451
|
+
balancer = RightSupport::Net::RequestBalancer.new([1,2,3])
|
452
|
+
balancer.request do |ep|
|
453
|
+
raise NoBigDeal, "Too many cows on the moon"
|
454
|
+
end
|
455
|
+
}.should raise_error(RightSupport::Net::NoResult)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
context 'when the health of an endpoint changes' do
|
460
|
+
it 'logs the change' do
|
461
|
+
health_check = Proc.new do |endpoint|
|
462
|
+
false
|
463
|
+
end
|
464
|
+
flexmock(@logger).should_receive(:info).times(8)
|
465
|
+
|
466
|
+
lambda {
|
467
|
+
balancer = RightSupport::Net::RequestBalancer.new([1,2,3,4], :policy => RightSupport::Net::LB::HealthCheck, :health_check => health_check)
|
468
|
+
balancer.request do |ep|
|
469
|
+
raise "Bad Endpoint"
|
470
|
+
end
|
471
|
+
}.should raise_error(RightSupport::Net::NoResult)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
context 'given a class health check policy' do
|
477
|
+
it 'retries and health checks the correct number of times' do
|
478
|
+
(1..10).to_a.each {|endpoint| test_bad_endpoint_requests(endpoint) }
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
context 'with :resolve option' do
|
483
|
+
before(:each) do
|
484
|
+
@endpoints = ['host1', 'host2', 'host3', 'host4']
|
485
|
+
@resolved_set_1 = {'host1' => ['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4']}
|
486
|
+
@resolved_set_2 = {'host1'=>['5.5.5.5'],'host2'=>['6.6.6.6'],'host3'=>['7.7.7.7'],'host4'=>['8.8.8.8']}
|
487
|
+
@resolved_set_2_array = []
|
488
|
+
@resolved_set_2.each_value{ |v| @resolved_set_2_array.concat(v) }
|
489
|
+
@dns = flexmock(RightSupport::Net::DNS)
|
490
|
+
end
|
491
|
+
|
492
|
+
it 'resolves ip addresses for specified list of endpoints' do
|
493
|
+
@dns.should_receive(:resolve_with_hostnames).with(@endpoints).and_return(@resolved_set_1)
|
494
|
+
@rb = RightSupport::Net::RequestBalancer.new(@endpoints, :resolve => 15)
|
495
|
+
|
496
|
+
@rb.request { true }
|
497
|
+
@policy = @rb.instance_variable_get("@policy")
|
498
|
+
@resolved_set_1['host1'].include?(@policy.next.first).should be_true
|
499
|
+
end
|
500
|
+
|
501
|
+
it 're-resolves list of ip addresses if TTL is expired' do
|
502
|
+
@dns.should_receive(:resolve_with_hostnames).with(@endpoints).twice.and_return(@resolved_set_1, @resolved_set_2)
|
503
|
+
@rb = RightSupport::Net::RequestBalancer.new(@endpoints, :resolve => 15)
|
504
|
+
|
505
|
+
@rb.request { true }
|
506
|
+
@policy = @rb.instance_variable_get("@policy")
|
507
|
+
@resolved_set_1['host1'].include?(@policy.next.first).should be_true
|
508
|
+
|
509
|
+
@rb.instance_variable_set("@resolved_at", Time.now.to_i - 16)
|
510
|
+
@rb.request { true }
|
511
|
+
@policy = @rb.instance_variable_get("@policy")
|
512
|
+
@resolved_set_2_array.include?(@policy.next.first).should be_true
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
context 'when a request raises NoResult' do
|
517
|
+
before(:each) do
|
518
|
+
@endpoints = [1,2,3,4,5,6]
|
519
|
+
@exceptions = [BigDeal, NoBigDeal, OtherTestException]
|
520
|
+
@rb = RightSupport::Net::RequestBalancer.new(@endpoints)
|
521
|
+
@tries = 0
|
522
|
+
begin
|
523
|
+
@rb.request do |ep|
|
524
|
+
@tries += 1
|
525
|
+
raise @exceptions[@tries % @exceptions.size]
|
526
|
+
end
|
527
|
+
rescue Exception => e
|
528
|
+
@raised = e
|
529
|
+
end
|
530
|
+
|
531
|
+
@raised.should be_kind_of RightSupport::Net::NoResult
|
532
|
+
end
|
533
|
+
|
534
|
+
it 'provides detailed information about the exceptions' do
|
535
|
+
# We should have details about all six endpoints
|
536
|
+
@raised.details.should_not be_nil
|
537
|
+
@raised.details.keys.size.should == 6
|
538
|
+
|
539
|
+
# Three exception types across six endpoints, means we should
|
540
|
+
# see each exception type appear twice
|
541
|
+
seen = {}
|
542
|
+
@raised.details.each_pair do |_, exceptions|
|
543
|
+
exceptions.each do |exception|
|
544
|
+
seen[exception.class] ||= []
|
545
|
+
seen[exception.class] << exception
|
546
|
+
end
|
547
|
+
end
|
548
|
+
seen.keys.size.should == 3
|
549
|
+
seen.values.all? { |v| v.size == 2}.should be_true
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
context :get_stats do
|
555
|
+
context 'using default balancing profile' do
|
556
|
+
it 'returns stats in an endpoint-keyed hash' do
|
557
|
+
expected_hash = {}
|
558
|
+
list = [1,2,3,4]
|
559
|
+
list.each { |k| expected_hash[k] = 'n/a' }
|
560
|
+
rb = RightSupport::Net::RequestBalancer.new(list)
|
561
|
+
|
562
|
+
rb.get_stats.should_not be_nil
|
563
|
+
rb.get_stats.should == expected_hash
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
context 'using health check balancing profile' do
|
568
|
+
it 'returns stats in an endpoint-keyed hash' do
|
569
|
+
expected_hash = {}
|
570
|
+
list = [1,2,3,4]
|
571
|
+
rb = RightSupport::Net::RequestBalancer.new(list,
|
572
|
+
:policy => RightSupport::Net::LB::HealthCheck,
|
573
|
+
:health_check => Proc.new {|endpoint| "HealthCheck passed for #{endpoint}!"})
|
574
|
+
rb.get_stats.should_not be_nil
|
575
|
+
rb.get_stats.should_not == expected_hash
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|