right_support 2.6.17 → 2.7.0

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