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