right_agent 1.0.1 → 2.0.7

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 (67) hide show
  1. data/README.rdoc +10 -8
  2. data/Rakefile +31 -5
  3. data/lib/right_agent.rb +6 -1
  4. data/lib/right_agent/actor.rb +4 -20
  5. data/lib/right_agent/actors/agent_manager.rb +1 -1
  6. data/lib/right_agent/agent.rb +357 -144
  7. data/lib/right_agent/agent_config.rb +7 -6
  8. data/lib/right_agent/agent_identity.rb +13 -11
  9. data/lib/right_agent/agent_tag_manager.rb +60 -64
  10. data/{spec/results_mock.rb → lib/right_agent/clients.rb} +10 -24
  11. data/lib/right_agent/clients/api_client.rb +383 -0
  12. data/lib/right_agent/clients/auth_client.rb +247 -0
  13. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  14. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  15. data/lib/right_agent/clients/right_http_client.rb +279 -0
  16. data/lib/right_agent/clients/router_client.rb +493 -0
  17. data/lib/right_agent/command/command_io.rb +4 -4
  18. data/lib/right_agent/command/command_parser.rb +2 -2
  19. data/lib/right_agent/command/command_runner.rb +1 -1
  20. data/lib/right_agent/connectivity_checker.rb +179 -0
  21. data/lib/right_agent/core_payload_types/secure_document_location.rb +2 -2
  22. data/lib/right_agent/dispatcher.rb +12 -10
  23. data/lib/right_agent/enrollment_result.rb +16 -12
  24. data/lib/right_agent/exceptions.rb +34 -20
  25. data/lib/right_agent/history.rb +10 -5
  26. data/lib/right_agent/log.rb +5 -5
  27. data/lib/right_agent/minimal.rb +1 -0
  28. data/lib/right_agent/multiplexer.rb +1 -1
  29. data/lib/right_agent/offline_handler.rb +270 -0
  30. data/lib/right_agent/packets.rb +7 -7
  31. data/lib/right_agent/payload_formatter.rb +1 -1
  32. data/lib/right_agent/pending_requests.rb +128 -0
  33. data/lib/right_agent/platform.rb +1 -1
  34. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  35. data/lib/right_agent/{idempotent_request.rb → retryable_request.rb} +7 -7
  36. data/lib/right_agent/scripts/agent_controller.rb +28 -26
  37. data/lib/right_agent/scripts/agent_deployer.rb +37 -22
  38. data/lib/right_agent/scripts/common_parser.rb +10 -3
  39. data/lib/right_agent/secure_identity.rb +1 -1
  40. data/lib/right_agent/sender.rb +299 -785
  41. data/lib/right_agent/serialize/secure_serializer.rb +3 -1
  42. data/lib/right_agent/serialize/secure_serializer_initializer.rb +2 -2
  43. data/lib/right_agent/serialize/serializable.rb +8 -3
  44. data/right_agent.gemspec +49 -18
  45. data/spec/agent_config_spec.rb +7 -7
  46. data/spec/agent_identity_spec.rb +7 -4
  47. data/spec/agent_spec.rb +43 -7
  48. data/spec/agent_tag_manager_spec.rb +72 -83
  49. data/spec/clients/api_client_spec.rb +423 -0
  50. data/spec/clients/auth_client_spec.rb +272 -0
  51. data/spec/clients/balanced_http_client_spec.rb +576 -0
  52. data/spec/clients/base_retry_client_spec.rb +635 -0
  53. data/spec/clients/router_client_spec.rb +594 -0
  54. data/spec/clients/spec_helper.rb +111 -0
  55. data/spec/command/command_io_spec.rb +1 -1
  56. data/spec/command/command_parser_spec.rb +1 -1
  57. data/spec/connectivity_checker_spec.rb +83 -0
  58. data/spec/dispatcher_spec.rb +3 -2
  59. data/spec/enrollment_result_spec.rb +2 -2
  60. data/spec/history_spec.rb +51 -39
  61. data/spec/offline_handler_spec.rb +340 -0
  62. data/spec/pending_requests_spec.rb +136 -0
  63. data/spec/{idempotent_request_spec.rb → retryable_request_spec.rb} +73 -73
  64. data/spec/sender_spec.rb +835 -1052
  65. data/spec/serialize/secure_serializer_spec.rb +3 -2
  66. data/spec/spec_helper.rb +54 -1
  67. metadata +71 -12
@@ -0,0 +1,495 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.join(File.dirname(__FILE__), '..', 'core_payload_types')
25
+
26
+ module RightScale
27
+
28
+ # Abstract base client for creating RightNet and RightApi clients with retry capability
29
+ # Requests are automatically retried to overcome connectivity failures
30
+ # A status callback is provided so that the user of the client can take action
31
+ # (e.g., queue requests) when connectivity is lost
32
+ # Health checks are sent periodically to try to recover from connectivity failures
33
+ class BaseRetryClient
34
+
35
+ # Interval between reconnect attempts
36
+ DEFAULT_RECONNECT_INTERVAL = 15
37
+
38
+ # Default time to wait for HTTP connection to open
39
+ DEFAULT_OPEN_TIMEOUT = 2
40
+
41
+ # Default time to wait for response from request, which is chosen to be 5 seconds greater
42
+ # than the response timeout inside the RightNet router
43
+ DEFAULT_REQUEST_TIMEOUT = 35
44
+
45
+ # Default interval between successive retries and default maximum elapsed time until stop retrying
46
+ # These are chosen to be consistent with the retry sequencing for RightNet retryable requests
47
+ # (per :retry_interval and :retry_timeout agent deployer configuration parameters for RightNet router),
48
+ # so that if the retrying happens within the router, it will not retry here
49
+ DEFAULT_RETRY_INTERVALS = [4, 12, 36]
50
+ DEFAULT_RETRY_TIMEOUT = 25
51
+
52
+ # State of this client: :pending, :connected, :disconnected, :failed, :closing, :closed
53
+ attr_reader :state
54
+
55
+ PERMITTED_STATE_TRANSITIONS = {
56
+ :pending => [:pending, :connected, :disconnected, :failed, :closed],
57
+ :connected => [:connected, :disconnected, :failed, :closing, :closed],
58
+ :disconnected => [:connected, :disconnected, :failed, :closed],
59
+ :failed => [:failed, :closed],
60
+ :closing => [:closing, :closed],
61
+ :closed => [:closed] }
62
+
63
+ # Set configuration of this client and initialize HTTP access
64
+ #
65
+ # @param [Symbol] type of server for use in obtaining URL from auth_client, e.g., :router
66
+ # @param [AuthClient] auth_client providing authorization session for HTTP requests
67
+ #
68
+ # @option options [String] :server_name for use in reporting errors, e.g., RightNet
69
+ # @option options [String] :api_version of server for use in X-API-Version header
70
+ # @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
71
+ # @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
72
+ # @option options [Numeric] :retry_timeout maximum before stop retrying; defaults to DEFAULT_RETRY_TIMEOUT
73
+ # @option options [Array] :retry_intervals between successive retries; defaults to DEFAULT_RETRY_INTERVALS
74
+ # @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
75
+ # @option options [Numeric] :reconnect_interval for reconnect attempts after lose connectivity
76
+ # @option options [Array] :filter_params symbols or strings for names of request parameters
77
+ # whose values are to be hidden when logging; can be augmented on individual requests
78
+ # @option options [Proc] :exception_callback for unexpected exceptions
79
+ #
80
+ # @return [Boolean] whether currently connected
81
+ #
82
+ # @raise [ArgumentError] auth client does not support this client type
83
+ # @raise [ArgumentError] :api_version missing
84
+ def init(type, auth_client, options)
85
+ raise ArgumentError, "Auth client does not support server type #{type.inspect}" unless auth_client.respond_to?(type.to_s + "_url")
86
+ raise ArgumentError, ":api_version option missing" unless options[:api_version]
87
+ @type = type
88
+ @auth_client = auth_client
89
+ @http_client = nil
90
+ @status_callbacks = []
91
+ @communicated_callbacks = []
92
+ @options = options.dup
93
+ @options[:server_name] ||= type.to_s
94
+ @options[:open_timeout] ||= DEFAULT_OPEN_TIMEOUT
95
+ @options[:request_timeout] ||= DEFAULT_REQUEST_TIMEOUT
96
+ @options[:retry_timeout] ||= DEFAULT_RETRY_TIMEOUT
97
+ @options[:retry_intervals] ||= DEFAULT_RETRY_INTERVALS
98
+ @options[:reconnect_interval] ||= DEFAULT_RECONNECT_INTERVAL
99
+ reset_stats
100
+ @state = :pending
101
+ create_http_client
102
+ enable_use if check_health == :connected
103
+ state == :connected
104
+ end
105
+
106
+ # Record callback to be notified of status changes
107
+ # Multiple callbacks are supported
108
+ #
109
+ # @yield [type, status] called when status changes (optional)
110
+ # @yieldparam [Symbol] type of client reporting status change
111
+ # @yieldparam [Symbol] state of client
112
+ #
113
+ # @return [Symbol] current state
114
+ def status(&callback)
115
+ @status_callbacks << callback if callback
116
+ state
117
+ end
118
+
119
+ # Set callback for each successful communication excluding health checks
120
+ # Multiple callbacks are supported
121
+ #
122
+ # @yield [] required block executed after successful communication
123
+ #
124
+ # @return [TrueClass] always true
125
+ def communicated(&callback)
126
+ @communicated_callbacks << callback if callback
127
+ true
128
+ end
129
+
130
+ # Take any actions necessary to quiesce client interaction in preparation
131
+ # for agent termination but allow any active requests to complete
132
+ #
133
+ # @param [Symbol] scope of close action: :receive for just closing receive side
134
+ # of client, :all for closing both receive and send side; defaults to :all
135
+ #
136
+ # @return [TrueClass] always true
137
+ def close(scope = :all)
138
+ if scope == :receive && state == :connected
139
+ self.state = :closing
140
+ else
141
+ self.state = :closed
142
+ @reconnect_timer.cancel if @reconnect_timer
143
+ @reconnect_timer = nil
144
+ end
145
+ true
146
+ end
147
+
148
+ # Current statistics for this client
149
+ #
150
+ # @param [Boolean] reset the statistics after getting the current ones
151
+ #
152
+ # @return [Hash] current statistics
153
+ # [Hash, NilClass] "reconnects" Activity stats or nil if none
154
+ # [Hash, NilClass] "request failures" Activity stats or nil if none
155
+ # [Hash, NilClass] "request sent" Activity stats or nil if none
156
+ # [Float, NilClass] "response time" average number of seconds to respond to a request or nil if none
157
+ # [Hash, NilClass] "state" Activity stats or nil if none
158
+ # [Hash, NilClass] "exceptions" Exceptions stats or nil if none
159
+ def stats(reset = false)
160
+ stats = {}
161
+ @stats.each { |k, v| stats[k] = v.all }
162
+ stats["response time"] = @stats["requests sent"].avg_duration
163
+ reset_stats if reset
164
+ stats
165
+ end
166
+
167
+ protected
168
+
169
+ # Reset statistics for this client
170
+ #
171
+ # @return [TrueClass] always true
172
+ def reset_stats
173
+ @stats = {
174
+ "reconnects" => RightSupport::Stats::Activity.new,
175
+ "request failures" => RightSupport::Stats::Activity.new,
176
+ "requests sent" => RightSupport::Stats::Activity.new,
177
+ "state" => RightSupport::Stats::Activity.new,
178
+ "exceptions" => RightSupport::Stats::Exceptions.new(agent = nil, @options[:exception_callback]) }
179
+ true
180
+ end
181
+
182
+ # Update state of this client
183
+ # If state has changed, make external callbacks to notify of change
184
+ # Do not update state once set to :closed
185
+ #
186
+ # @param [Hash] value for new state
187
+ #
188
+ # @return [Symbol] updated state
189
+ #
190
+ # @raise [ArgumentError] invalid state transition
191
+ def state=(value)
192
+ if @state != :closed
193
+ unless PERMITTED_STATE_TRANSITIONS[@state].include?(value)
194
+ raise ArgumentError, "Invalid state transition: #{@state.inspect} -> #{value.inspect}"
195
+ end
196
+
197
+ case value
198
+ when :pending, :closing, :closed
199
+ @stats["state"].update(value.to_s)
200
+ @state = value
201
+ when :connected, :disconnected, :failed
202
+ if value != @state
203
+ @stats["state"].update(value.to_s)
204
+ @state = value
205
+ @status_callbacks.each do |callback|
206
+ begin
207
+ callback.call(@type, @state)
208
+ rescue StandardError => e
209
+ Log.error("Failed status callback", e)
210
+ @stats["exceptions"].track("status", e)
211
+ end
212
+ end
213
+ reconnect if @state == :disconnected
214
+ end
215
+ end
216
+ end
217
+ @state
218
+ end
219
+
220
+ # Create HTTP client
221
+ #
222
+ # @return [TrueClass] always true
223
+ #
224
+ # @return [RightSupport::Net::BalancedHttpClient] client
225
+ def create_http_client
226
+ url = @auth_client.send(@type.to_s + "_url")
227
+ Log.info("Connecting to #{@options[:server_name]} via #{url.inspect}")
228
+ options = {
229
+ :server_name => @options[:server_name],
230
+ :open_timeout => @options[:open_timeout],
231
+ :request_timeout => @options[:request_timeout] }
232
+ options[:api_version] = @options[:api_version] if @options[:api_version]
233
+ options[:filter_params] = @options[:filter_params] if @options[:filter_params]
234
+ @http_client = RightScale::BalancedHttpClient.new(url, options)
235
+ end
236
+
237
+ # Perform any other steps needed to make this client fully usable
238
+ # once HTTP client has been created and server known to be accessible
239
+ #
240
+ # @return [TrueClass] always true
241
+ def enable_use
242
+ true
243
+ end
244
+
245
+ # Check health of RightApi directly without applying RequestBalancer
246
+ # Do not check whether HTTP client exists
247
+ #
248
+ # @return [Symbol] RightApi client state
249
+ def check_health
250
+ begin
251
+ @http_client.check_health
252
+ self.state = :connected
253
+ rescue BalancedHttpClient::NotResponding => e
254
+ Log.error("Failed #{@options[:server_name]} health check", e.nested_exception)
255
+ self.state = :disconnected
256
+ rescue Exception => e
257
+ Log.error("Failed #{@options[:server_name]} health check", e)
258
+ @stats["exceptions"].track("check health", e)
259
+ self.state = :disconnected
260
+ end
261
+ end
262
+
263
+ # Reconnect with server by periodically checking health
264
+ # Randomize when initially start checking to reduce server spiking
265
+ #
266
+ # @return [TrueClass] always true
267
+ def reconnect
268
+ unless @reconnecting
269
+ @reconnecting = true
270
+ @stats["reconnects"].update("initiate")
271
+ @reconnect_timer = EM::PeriodicTimer.new(rand(@options[:reconnect_interval])) do
272
+ begin
273
+ create_http_client
274
+ if check_health == :connected
275
+ enable_use
276
+ @stats["reconnects"].update("success")
277
+ @reconnect_timer.cancel if @reconnect_timer # only need 'if' for test purposes
278
+ @reconnect_timer = @reconnecting = nil
279
+ end
280
+ rescue Exception => e
281
+ Log.error("Failed #{@options[:server_name]} reconnect", e)
282
+ @stats["reconnects"].update("failure")
283
+ @stats["exceptions"].track("reconnect", e)
284
+ self.state = :disconnected
285
+ end
286
+ @reconnect_timer.interval = @options[:reconnect_interval] if @reconnect_timer
287
+ end
288
+ end
289
+ true
290
+ end
291
+
292
+ # Make request via HTTP
293
+ # Rely on underlying HTTP client to log request and response
294
+ # Retry request if response indicates to or if there are connectivity failures
295
+ #
296
+ # There are also several timeouts involved:
297
+ # - Underlying BalancedHttpClient connection open timeout (:open_timeout)
298
+ # - Underlying BalancedHttpClient request timeout (:request_timeout)
299
+ # - Retry timeout for this method and its handlers (:retry_timeout)
300
+ # and if the target server is a RightNet router:
301
+ # - Router response timeout (ideally > :retry_timeout and < :request_timeout)
302
+ # - Router retry timeout (ideally = :retry_timeout)
303
+ #
304
+ # There are several possible levels of retry involved, starting with the outermost:
305
+ # - This method will retry if the targeted server is not responding or if it receives
306
+ # a retry response, but the total elapsed time is not allowed to exceed :request_timeout
307
+ # - RequestBalancer in BalancedHttpClient will retry using other endpoints if it gets an error
308
+ # that it considers retryable, and even if a front-end balancer is in use there will
309
+ # likely be at least two such endpoints for redundancy
310
+ # and if the target server is a RightNet router:
311
+ # - The router when sending a request via AMQP will retry if it receives no response,
312
+ # but not exceeding its configured :retry_timeout; if the router's timeouts for retry
313
+ # are consistent with the ones prescribed above, there will be no retry by the
314
+ # RequestBalancer after router retries
315
+ #
316
+ # @param [Symbol] verb for HTTP REST request
317
+ # @param [String] path in URI for desired resource
318
+ # @param [Hash] params for HTTP request
319
+ # @param [String] type of request for use in logging; defaults to path
320
+ # @param [String, NilClass] request_uuid uniquely identifying this request;
321
+ # defaults to randomly generated UUID
322
+ # @param [Hash] options augmenting or overriding default options for HTTP request
323
+ #
324
+ # @return [Object, NilClass] result of request with nil meaning no result
325
+ #
326
+ # @raise [Exceptions::Unauthorized] authorization failed
327
+ # @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
328
+ # to it, or it is out of service or too busy to respond
329
+ # @raise [Exceptions::RetryableError] request failed but if retried may succeed
330
+ # @raise [Exceptions::Terminating] closing client and terminating service
331
+ # @raise [Exceptions::InternalServerError] internal error in server being accessed
332
+ def make_request(verb, path, params = {}, type = nil, request_uuid = nil, options = {})
333
+ raise Exceptions::Terminating if state == :closed
334
+ request_uuid ||= RightSupport::Data::UUID.generate
335
+ started_at = Time.now
336
+ attempts = 0
337
+ result = nil
338
+ @stats["requests sent"].measure(type || path, request_uuid) do
339
+ begin
340
+ attempts += 1
341
+ http_options = {
342
+ :open_timeout => @options[:open_timeout],
343
+ :request_timeout => @options[:request_timeout],
344
+ :request_uuid => request_uuid,
345
+ :headers => @auth_client.headers }
346
+ raise Exceptions::ConnectivityFailure, "#{@type} client not connected" unless [:connected, :closing].include?(state)
347
+ result = @http_client.send(verb, path, params, http_options.merge(options))
348
+ rescue StandardError => e
349
+ request_uuid = handle_exception(e, type || path, request_uuid, started_at, attempts)
350
+ request_uuid ? retry : raise
351
+ end
352
+ end
353
+ @communicated_callbacks.each { |callback| callback.call }
354
+ result
355
+ end
356
+
357
+ # Examine exception to determine whether to setup retry, raise new exception, or re-raise
358
+ #
359
+ # @param [StandardError] exception raised
360
+ # @param [String] action from request type
361
+ # @param [String] type of request for use in logging
362
+ # @param [String] request_uuid originally created for this request
363
+ # @param [Time] started_at time for request
364
+ # @param [Integer] attempts to make request
365
+ #
366
+ # @return [String, NilClass] request token to be used on retry or nil if to raise instead
367
+ #
368
+ # @raise [Exceptions::Unauthorized] authorization failed
369
+ # @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
370
+ # to it, or it is out of service or too busy to respond
371
+ # @raise [Exceptions::RetryableError] request failed but if retried may succeed
372
+ # @raise [Exceptions::InternalServerError] internal error in server being accessed
373
+ def handle_exception(exception, type, request_uuid, started_at, attempts)
374
+ result = request_uuid
375
+ if exception.respond_to?(:http_code)
376
+ case exception.http_code
377
+ when 301, 302 # MovedPermanently, Found
378
+ handle_redirect(exception, type, request_uuid)
379
+ when 401 # Unauthorized
380
+ raise Exceptions::Unauthorized.new(exception.http_body, exception)
381
+ when 403 # Forbidden
382
+ @auth_client.expired
383
+ raise Exceptions::RetryableError.new("Authorization expired", exception)
384
+ when 449 # RetryWith
385
+ result = handle_retry_with(exception, type, request_uuid, started_at, attempts)
386
+ when 500 # InternalServerError
387
+ raise Exceptions::InternalServerError.new(exception.http_body, @options[:server_name])
388
+ else
389
+ @stats["request failures"].update("#{type} - #{exception.http_code}")
390
+ result = nil
391
+ end
392
+ elsif exception.is_a?(BalancedHttpClient::NotResponding)
393
+ handle_not_responding(exception, type, request_uuid, started_at, attempts)
394
+ else
395
+ @stats["request failures"].update("#{type} - #{exception.class.name}")
396
+ result = nil
397
+ end
398
+ result
399
+ end
400
+
401
+ # Treat redirect response as indication that no longer accessing the correct shard
402
+ # Handle it by informing auth client so that it can re-authorize
403
+ # Do not retry, but tell client to with the expectation that re-auth will correct the situation
404
+ #
405
+ # @param [RestClient::MovedPermanently, RestClient::Found] redirect exception raised
406
+ # @param [String] type of request for use in logging
407
+ # @param [String] request_uuid originally created for this request
408
+ #
409
+ # @return [TrueClass] never returns
410
+ #
411
+ # @raise [Exceptions::RetryableError] request redirected but if retried may succeed
412
+ # @raise [Exceptions::InternalServerError] no redirect location provided
413
+ def handle_redirect(redirect, type, request_uuid)
414
+ Log.info("Received REDIRECT #{redirect} for #{type} request <#{request_uuid}>")
415
+ if redirect.respond_to?(:response) && (location = redirect.response.headers[:location]) && !location.empty?
416
+ Log.info("Requesting auth client to handle redirect to #{location.inspect}")
417
+ @stats["reconnects"].update("redirect")
418
+ @auth_client.redirect(location)
419
+ raise Exceptions::RetryableError.new(redirect.http_body, redirect)
420
+ else
421
+ raise Exceptions::InternalServerError.new("No redirect location provided", @options[:server_name])
422
+ end
423
+ true
424
+ end
425
+
426
+ # Handle retry response by retrying it once
427
+ # This indicates the request was received but a retryable error prevented
428
+ # it from being processed; the retry responsibility may be passed on
429
+ # If retrying, this function does not return until it is time to retry
430
+ #
431
+ # @param [RestClient::RetryWith] retry_result exception raised
432
+ # @param [String] type of request for use in logging
433
+ # @param [String] request_uuid originally created for this request
434
+ # @param [Time] started_at time for request
435
+ # @param [Integer] attempts to make request
436
+ #
437
+ # @return [String] request token to be used on retry
438
+ #
439
+ # @raise [Exceptions::RetryableError] request failed but if retried may succeed
440
+ def handle_retry_with(retry_result, type, request_uuid, started_at, attempts)
441
+ if @options[:retry_enabled]
442
+ interval = @options[:retry_intervals][attempts - 1]
443
+ if attempts == 1 && interval && (Time.now - started_at) < @options[:retry_timeout]
444
+ Log.error("Retrying #{type} request <#{request_uuid}> in #{interval} seconds " +
445
+ "in response to retryable error (#{retry_result.http_body})")
446
+ sleep(interval)
447
+ else
448
+ @stats["request failures"].update("#{type} - retry")
449
+ raise Exceptions::RetryableError.new(retry_result.http_body, retry_result)
450
+ end
451
+ else
452
+ @stats["request failures"].update("#{type} - retry")
453
+ raise Exceptions::RetryableError.new(retry_result.http_body, retry_result)
454
+ end
455
+ # Change request_uuid so that retried request not rejected as duplicate
456
+ "#{request_uuid}:retry"
457
+ end
458
+
459
+ # Handle not responding response by determining whether okay to retry
460
+ # If request is being retried, this function does not return until it is time to retry
461
+ #
462
+ # @param [RightScale::BalancedHttpClient::NotResponding] not_responding exception
463
+ # indicating targeted server is too busy or out of service
464
+ # @param [String] type of request for use in logging
465
+ # @param [String] request_uuid originally created for this request
466
+ # @param [Time] started_at time for request
467
+ # @param [Integer] attempts to make request
468
+ #
469
+ # @return [TrueClass] always true
470
+ #
471
+ # @raise [Exceptions::ConnectivityFailure] cannot connect to server, lost connection
472
+ # to it, or it is out of service or too busy to respond
473
+ def handle_not_responding(not_responding, type, request_uuid, started_at, attempts)
474
+ if @options[:retry_enabled]
475
+ interval = @options[:retry_intervals][attempts - 1]
476
+ if interval && (Time.now - started_at) < @options[:retry_timeout]
477
+ Log.error("Retrying #{type} request <#{request_uuid}> in #{interval} seconds " +
478
+ "in response to routing failure (#{BalancedHttpClient.exception_text(not_responding)})")
479
+ sleep(interval)
480
+ else
481
+ @stats["request failures"].update("#{type} - no result")
482
+ self.state = :disconnected
483
+ raise Exceptions::ConnectivityFailure.new(not_responding.message + " after #{attempts} attempts")
484
+ end
485
+ else
486
+ @stats["request failures"].update("#{type} - no result")
487
+ self.state = :disconnected
488
+ raise Exceptions::ConnectivityFailure.new(not_responding.message)
489
+ end
490
+ true
491
+ end
492
+
493
+ end # BaseRetryClient
494
+
495
+ end # RightScale