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,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