right_agent 1.0.1 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -77,17 +77,17 @@ module RightScale
77
77
  # :graceful_exits(Integer|nil):: Number of graceful terminations, if any
78
78
  # :crashes(Integer|nil):: Number of crashes, if any
79
79
  # :last_crash_time(Integer|nil):: Time in seconds in Unix-epoch when last crash occurred, if any
80
+ # :crashed_last(Boolean):: Whether crashed last time it was started
80
81
  def analyze_service
81
82
  now = Time.now.to_i
82
83
  if @last_analysis && @last_event == @last_update
83
- if @last_analysis[:uptime] > 0
84
- delta = now - @last_analysis_time
85
- @last_analysis[:uptime] += delta
86
- @last_analysis[:total_uptime] += delta
87
- end
84
+ delta = now - @last_analysis_time
85
+ @last_analysis[:uptime] += delta
86
+ @last_analysis[:total_uptime] += delta
88
87
  else
89
88
  last_run = last_crash = @last_event = {:time => 0, :pid => 0, :event => nil}
90
89
  restarts = graceful_exits = crashes = accumulated_uptime = 0
90
+ crashed_last = false
91
91
  load.each do |event|
92
92
  event = SerializationHelper.symbolize_keys(event)
93
93
  case event[:event]
@@ -98,19 +98,23 @@ module RightScale
98
98
  when "start"
99
99
  crashes += 1
100
100
  last_crash = event
101
+ crashed_last = true
101
102
  when "run"
102
103
  crashes += 1
103
104
  last_crash = event
105
+ crashed_last = true
104
106
  # Accumulating uptime here although this will wrongly include recovery time
105
107
  accumulated_uptime += (event[:time] - @last_event[:time])
106
108
  end
107
109
  when "run"
108
110
  last_run = event
109
111
  when "stop"
112
+ crashed_last = false
110
113
  if @last_event[:event] == "run" && @last_event[:pid] == event[:pid]
111
114
  accumulated_uptime += (event[:time] - @last_event[:time])
112
115
  end
113
116
  when "graceful exit"
117
+ crashed_last = false
114
118
  graceful_exits += 1
115
119
  else
116
120
  next
@@ -129,6 +133,7 @@ module RightScale
129
133
  if crashes > 0
130
134
  @last_analysis[:crashes] = crashes
131
135
  @last_analysis[:last_crash_time] = last_crash[:time]
136
+ @last_analysis[:crashed_last] = crashed_last
132
137
  end
133
138
  end
134
139
  @last_analysis_time = now
@@ -133,7 +133,7 @@ module RightScale
133
133
  def method_missing(m, *args)
134
134
  init unless @initialized
135
135
  @logger.level = level_from_sym(level) if @level_frozen
136
- @logger.__send__(m, *args)
136
+ @logger.send(m, *args)
137
137
  end
138
138
 
139
139
  # Determine whether this object, or its method_missing proxy, responds
@@ -218,9 +218,9 @@ module RightScale
218
218
  # lvl(Constant):: One of Logger::DEBUG ... Logger::FATAL
219
219
  #
220
220
  # === Raise
221
- # (RightScale::Exceptions::Argument):: if level symbol is invalid
221
+ # (ArgumentError):: if level symbol is invalid
222
222
  def level_from_sym(sym)
223
- raise Exceptions::Argument, "Invalid log level symbol :#{sym}" unless LEVELS_MAP.include?(sym)
223
+ raise ArgumentError, "Invalid log level symbol :#{sym}" unless LEVELS_MAP.include?(sym)
224
224
  lvl = LEVELS_MAP[sym]
225
225
  end
226
226
 
@@ -233,10 +233,10 @@ module RightScale
233
233
  # sym(Symbol):: One of :debug, :info, :warn, :error or :fatal
234
234
  #
235
235
  # === Raise
236
- # (RightScale::Exceptions::Argument):: if level is invalid
236
+ # (ArgumentError):: if level is invalid
237
237
  def level_to_sym(lvl)
238
238
  @@inverted_levels_map ||= LEVELS_MAP.invert
239
- raise Exceptions::Argument, "Invalid log level: #{lvl}" unless @@inverted_levels_map.include?(lvl)
239
+ raise ArgumentError, "Invalid log level: #{lvl}" unless @@inverted_levels_map.include?(lvl)
240
240
  sym = @@inverted_levels_map[lvl]
241
241
  end
242
242
 
@@ -39,6 +39,7 @@ end
39
39
  # requires are oriented toward that. any additional use cases may require a
40
40
  # rethink of minimal loading.
41
41
  require ::File.normalize_path('agent_config', RIGHT_AGENT_BASE_DIR)
42
+ require ::File.normalize_path('protocol_version_mixin', RIGHT_AGENT_BASE_DIR)
42
43
  require ::File.normalize_path('command', RIGHT_AGENT_BASE_DIR)
43
44
  require ::File.normalize_path('log', RIGHT_AGENT_BASE_DIR)
44
45
  require ::File.normalize_path('pid_file', RIGHT_AGENT_BASE_DIR)
@@ -83,7 +83,7 @@ module RightScale
83
83
  # === Return
84
84
  # res(Object):: Result of first target in list
85
85
  def method_missing(m, *args)
86
- res = @targets.inject([]) { |res, t| res << t.__send__(m, *args) }
86
+ res = @targets.inject([]) { |res, t| res << t.send(m, *args) }
87
87
  res[0]
88
88
  end
89
89
 
@@ -0,0 +1,270 @@
1
+ #
2
+ # Copyright (c) 2009-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
+ module RightScale
24
+
25
+ # Handler for queueing of requests when offline relative to RightNet
26
+ # and then sending the requests when successfully reconnect
27
+ class OfflineHandler
28
+
29
+ # Maximum seconds to wait before starting flushing offline queue when disabling offline mode
30
+ MAX_QUEUE_FLUSH_DELAY = 2 * 60
31
+
32
+ # Maximum number of offline queued requests before triggering restart vote
33
+ MAX_QUEUED_REQUESTS = 100
34
+
35
+ # Number of seconds that should be spent in offline mode before triggering a restart vote
36
+ RESTART_VOTE_DELAY = 15 * 60
37
+
38
+ # (Symbol) Current queue state with possible values:
39
+ # Value Description Action Next state
40
+ # :created Queue created init :initializing
41
+ # :initializing Agent still initializing start :running
42
+ # :running Queue has been started disable when offline :flushing
43
+ # :flushing Sending queued requests enable :running
44
+ # :terminating Agent terminating
45
+ attr_reader :state
46
+
47
+ # (Symbol) Current offline handling mode with possible values:
48
+ # Value Description
49
+ # :initializing Agent still initializing
50
+ # :online Agent connected
51
+ # :offline Agent disconnected
52
+ attr_reader :mode
53
+
54
+ # (Array) Offline queue
55
+ attr_accessor :queue
56
+
57
+ # Create offline queueing handler
58
+ #
59
+ # === Parameters
60
+ # restart_callback(Proc):: Callback that is activated on each restart vote with votes being initiated
61
+ # by offline queue exceeding MAX_QUEUED_REQUESTS
62
+ # offline_stats(RightSupport::Stats::Activity):: Offline queue tracking statistics
63
+ def initialize(restart_callback, offline_stats)
64
+ @restart_vote = restart_callback
65
+ @restart_vote_timer = nil
66
+ @restart_vote_count = 0
67
+ @offline_stats = offline_stats
68
+ @state = :created
69
+ @mode = :initializing
70
+ @queue = []
71
+ end
72
+
73
+ # Initialize the offline queue
74
+ # All requests sent prior to running this initialization are queued
75
+ # and then are sent once this initialization has run
76
+ # All requests following this call and prior to calling start
77
+ # are prepended to the request queue
78
+ #
79
+ # === Return
80
+ # true:: Always return true
81
+ def init
82
+ @state = :initializing if @state == :created
83
+ true
84
+ end
85
+
86
+ # Switch to online mode and send all buffered messages
87
+ #
88
+ # === Return
89
+ # true:: Always return true
90
+ def start
91
+ if @state == :initializing
92
+ if @mode == :offline
93
+ @state = :running
94
+ else
95
+ @state = :flushing
96
+ flush
97
+ end
98
+ @mode = :online if @mode == :initializing
99
+ end
100
+ true
101
+ end
102
+
103
+ # Is agent currently offline?
104
+ #
105
+ # === Return
106
+ # (Boolean):: true if agent offline, otherwise false
107
+ def offline?
108
+ @mode == :offline || @state == :created
109
+ end
110
+
111
+ # In request queueing mode?
112
+ #
113
+ # === Return
114
+ # (Boolean):: true if should queue request, otherwise false
115
+ def queueing?
116
+ offline? && @state != :flushing
117
+ end
118
+
119
+ # Switch to offline mode
120
+ # In this mode requests are queued in memory rather than being sent
121
+ # Idempotent
122
+ #
123
+ # === Return
124
+ # true:: Always return true
125
+ def enable
126
+ if offline?
127
+ if @state == :flushing
128
+ # If we were in offline mode then switched back to online but are still in the
129
+ # process of flushing the in-memory queue and are now switching to offline mode
130
+ # again then stop the flushing
131
+ @state = :running
132
+ end
133
+ else
134
+ Log.info("[offline] Disconnect from RightNet detected, entering offline mode")
135
+ Log.info("[offline] Messages will be queued in memory until RightNet connection is re-established")
136
+ @offline_stats.update
137
+ @queue ||= [] # Ensure queue is valid without losing any messages when going offline
138
+ @mode = :offline
139
+ start_timer
140
+ end
141
+ true
142
+ end
143
+
144
+ # Switch back to sending requests after in-memory queue gets flushed
145
+ # Idempotent
146
+ #
147
+ # === Return
148
+ # true:: Always return true
149
+ def disable
150
+ if offline? && @state != :created
151
+ Log.info("[offline] Connection to RightNet re-established")
152
+ @offline_stats.finish
153
+ cancel_timer
154
+ @state = :flushing
155
+ # Wait a bit to avoid flooding RightNet
156
+ EM.add_timer(rand(MAX_QUEUE_FLUSH_DELAY)) { flush }
157
+ end
158
+ true
159
+ end
160
+
161
+ # Queue given request in memory
162
+ #
163
+ # === Parameters
164
+ # request(Hash):: Request to be stored
165
+ #
166
+ # === Return
167
+ # true:: Always return true
168
+ def queue_request(kind, type, payload, target, callback)
169
+ request = {:kind => kind, :type => type, :payload => payload, :target => target, :callback => callback}
170
+ Log.info("[offline] Queuing request: #{request.inspect}")
171
+ vote_to_restart if (@restart_vote_count += 1) >= MAX_QUEUED_REQUESTS
172
+ if @state == :initializing
173
+ # We are in the initialization callback, requests should be put at the head of the queue
174
+ @queue.unshift(request)
175
+ else
176
+ @queue << request
177
+ end
178
+ true
179
+ end
180
+
181
+ # Prepare for agent termination
182
+ #
183
+ # === Return
184
+ # true:: Always return true
185
+ def terminate
186
+ @state = :terminating
187
+ cancel_timer
188
+ true
189
+ end
190
+
191
+ protected
192
+
193
+ # Send any requests that were queued while in offline mode
194
+ # Do this asynchronously to allow for agents to respond to requests
195
+ # Once all in-memory requests have been flushed, switch off offline mode
196
+ #
197
+ # === Parameters
198
+ # again(Boolean):: Whether being called in a loop
199
+ #
200
+ # === Return
201
+ # true:: Always return true
202
+ def flush(again = false)
203
+ if @state == :flushing
204
+ Log.info("[offline] Starting to flush request queue of size #{@queue.size}") unless again || @mode == :initializing
205
+ if @queue.any?
206
+ r = @queue.shift
207
+ if r[:callback]
208
+ Sender.instance.send(r[:kind], r[:type], r[:payload], r[:target]) { |result| r[:callback].call(result) }
209
+ else
210
+ Sender.instance.send(r[:kind], r[:type], r[:payload], r[:target])
211
+ end
212
+ end
213
+ if @queue.empty?
214
+ Log.info("[offline] Request queue flushed, resuming normal operations") unless @mode == :initializing
215
+ @mode = :online
216
+ @state = :running
217
+ else
218
+ EM.next_tick { flush(true) }
219
+ end
220
+ end
221
+ true
222
+ end
223
+
224
+ # Vote for restart and reset trigger
225
+ #
226
+ # === Parameters
227
+ # timer_trigger(Boolean):: true if vote was triggered by timer, false if it
228
+ # was triggered by number of messages in in-memory queue
229
+ #
230
+ # === Return
231
+ # true:: Always return true
232
+ def vote_to_restart(timer_trigger = false)
233
+ if @restart_vote
234
+ @restart_vote.call
235
+ if timer_trigger
236
+ start_timer
237
+ else
238
+ @restart_vote_count = 0
239
+ end
240
+ end
241
+ true
242
+ end
243
+
244
+ # Start restart vote timer
245
+ #
246
+ # === Return
247
+ # true:: Always return true
248
+ def start_timer
249
+ if @restart_vote && @state != :terminating
250
+ @restart_vote_timer ||= EM::Timer.new(RESTART_VOTE_DELAY) { vote_to_restart(timer_trigger = true) }
251
+ end
252
+ true
253
+ end
254
+
255
+ # Cancel restart vote timer
256
+ #
257
+ # === Return
258
+ # true:: Always return true
259
+ def cancel_timer
260
+ if @restart_vote_timer
261
+ @restart_vote_timer.cancel
262
+ @restart_vote_timer = nil
263
+ @restart_vote_count = 0
264
+ end
265
+ true
266
+ end
267
+
268
+ end # OfflineHandler
269
+
270
+ end # RightScale
@@ -40,7 +40,7 @@ end
40
40
 
41
41
  module RightScale
42
42
 
43
- # Base class for all packets flowing through the mappers
43
+ # Base class for all packets flowing through the RightNet routers
44
44
  # Knows how to dump itself to MessagePack or JSON
45
45
  class Packet
46
46
 
@@ -156,7 +156,7 @@ module RightScale
156
156
  # === Return
157
157
  # log_msg(String):: Log representation
158
158
  def to_s(filter = nil, version = nil)
159
- v = __send__(version) if version
159
+ v = send(version) if version
160
160
  v = (v && v != DEFAULT_VERSION) ? " v#{v}" : ""
161
161
  log_msg = "[#{name}#{v}]"
162
162
  duration = if @duration && (filter.nil? || filter.include?(:duration))
@@ -295,8 +295,8 @@ module RightScale
295
295
  # opts(Hash):: Optional settings:
296
296
  # :from(String):: Sender identity
297
297
  # :scope(Hash):: Define behavior that should be used to resolve tag based routing
298
- # :token(String):: Generated request id that a mapper uses to identify replies
299
- # :reply_to(String):: Identity of the node that actor replies to, usually a mapper itself
298
+ # :token(String):: Generated request id that a router uses to identify replies
299
+ # :reply_to(String):: Identity of the node that actor replies to, usually a router itself
300
300
  # :selector(Symbol):: Selector used to route the request: :any or :all, defaults to :any,
301
301
  # :all deprecated for version 13 and above
302
302
  # :target(String|Array):: Target recipient(s)
@@ -426,12 +426,12 @@ module RightScale
426
426
  # opts(Hash):: Optional settings:
427
427
  # :from(String):: Sender identity
428
428
  # :scope(Hash):: Define behavior that should be used to resolve tag based routing
429
- # :token(String):: Generated request id that a mapper uses to identify replies
429
+ # :token(String):: Generated request id that a router uses to identify replies
430
430
  # :selector(Symbol):: Selector used to route the request: :any or :all, defaults to :any
431
431
  # :target(String|Array):: Target recipient(s)
432
432
  # :persistent(Boolean):: Indicates if this request should be saved to persistent storage
433
433
  # by the AMQP broker
434
- # :confirm(Boolean):: Whether require confirmation response from mapper containing targets
434
+ # :confirm(Boolean):: Whether require confirmation response from router containing targets
435
435
  # to which request was published but not necessarily delivered
436
436
  # :expires_at(Integer|nil):: Time in seconds in Unix-epoch when this request expires and
437
437
  # is to be ignored by the receiver; value 0 means never expire; defaults to 0
@@ -532,7 +532,7 @@ module RightScale
532
532
  # Create packet
533
533
  #
534
534
  # === Parameters
535
- # token(String):: Generated request id that a mapper uses to identify replies
535
+ # token(String):: Generated request id that a router uses to identify replies
536
536
  # to(String):: Identity of the node to which result should be delivered
537
537
  # results(Any):: Arbitrary data that is transferred from actor, a result of actor's work
538
538
  # from(String):: Sender identity
@@ -39,7 +39,7 @@ module RightScale
39
39
  parts = type.split('/')
40
40
  meth = "#{parts[1]}_#{parts[2]}".to_sym
41
41
  res = nil
42
- res = @formatter.__send__(meth, payload) if @formatter.respond_to?(meth)
42
+ res = @formatter.send(meth, payload) if @formatter.respond_to?(meth)
43
43
  res
44
44
  end
45
45
 
@@ -0,0 +1,128 @@
1
+ #
2
+ # Copyright (c) 2009-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
+ module RightScale
24
+
25
+ # Request that is waiting for a response
26
+ class PendingRequest
27
+
28
+ # (Symbol) Kind of request: :send_push or :send_request
29
+ attr_reader :kind
30
+
31
+ # (Time) Time when request message was received
32
+ attr_reader :receive_time
33
+
34
+ # (Proc) Block to be activated when response is received
35
+ attr_reader :response_handler
36
+
37
+ # (String) Token for parent request in a retry situation
38
+ attr_accessor :retry_parent_token
39
+
40
+ # (String) Non-delivery reason if any
41
+ attr_accessor :non_delivery
42
+
43
+ def initialize(kind, receive_time, response_handler)
44
+ @kind = kind
45
+ @receive_time = receive_time
46
+ @response_handler = response_handler
47
+ @retry_parent_token = nil
48
+ @non_delivery = nil
49
+ end
50
+
51
+ end # PendingRequest
52
+
53
+ # Cache for requests that are waiting for a response
54
+ # Automatically deletes push requests when get too old
55
+ # Retains non-push requests until explicitly deleted
56
+ class PendingRequests < Hash
57
+
58
+ # Maximum number of seconds to retain send pushes in cache
59
+ MAX_PUSH_AGE = 2 * 60
60
+
61
+ # Minimum number of seconds between push cleanups
62
+ MIN_CLEANUP_INTERVAL = 15
63
+
64
+ # Create cache
65
+ def initialize
66
+ @last_cleanup = Time.now
67
+ super
68
+ end
69
+
70
+ # Store pending request
71
+ #
72
+ # === Parameters
73
+ # token(String):: Generated message identifier
74
+ # pending_request(PendingRequest):: Pending request
75
+ #
76
+ # === Return
77
+ # (PendingRequest):: Stored request
78
+ def []=(token, pending_request)
79
+ now = Time.now
80
+ if (now - @last_cleanup) > MIN_CLEANUP_INTERVAL
81
+ self.reject! { |t, r| r.kind == :send_push && (now - r.receive_time) > MAX_PUSH_AGE }
82
+ @last_cleanup = now
83
+ end
84
+ super
85
+ end
86
+
87
+ # Select cache entries of the given kind
88
+ #
89
+ # === Parameters
90
+ # kind(Symbol):: Kind of request to be included: :send_push or :send_request
91
+ #
92
+ # === Return
93
+ # (Hash):: Requests of specified kind
94
+ def kind(kind)
95
+ self.reject { |t, r| r.kind != kind}
96
+ end
97
+
98
+ # Get age of youngest pending request
99
+ #
100
+ # === Return
101
+ # age(Integer):: Age of youngest request
102
+ def youngest_age
103
+ now = Time.now
104
+ age = nil
105
+ self.each_value do |r|
106
+ seconds = (now - r.receive_time).to_i
107
+ age = seconds if age.nil? || seconds < age
108
+ end
109
+ age
110
+ end
111
+
112
+ # Get age of oldest pending request
113
+ #
114
+ # === Return
115
+ # age(Integer):: Age of oldest request
116
+ def oldest_age
117
+ now = Time.now
118
+ age = nil
119
+ self.each_value do |r|
120
+ seconds = (now - r.receive_time).to_i
121
+ age = seconds if age.nil? || seconds > age
122
+ end
123
+ age
124
+ end
125
+
126
+ end # PendingRequests
127
+
128
+ end # RightScale