right_agent 2.1.5-x86-mingw32 → 2.2.0-x86-mingw32

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.
@@ -112,7 +112,7 @@ module RightScale
112
112
  result
113
113
  end
114
114
 
115
- # Make long-polling requests until receive data or timeout
115
+ # Make long-polling requests until receive data, hit error, or timeout
116
116
  #
117
117
  # @param [Hash] connection to server from previous request with keys :host, :path,
118
118
  # and :expires_at, with the :expires_at being adjusted on return
@@ -131,6 +131,16 @@ module RightScale
131
131
  [result, code, body, headers]
132
132
  end
133
133
 
134
+ # Close all persistent connections
135
+ #
136
+ # @param [String] reason for closing
137
+ #
138
+ # @return [TrueClass] always true
139
+ def close(reason)
140
+ @connections = {}
141
+ true
142
+ end
143
+
134
144
  protected
135
145
 
136
146
  # Make HTTP request once
@@ -123,17 +123,15 @@ module RightScale
123
123
  # Make request an then yield fiber until it completes
124
124
  fiber = Fiber.current
125
125
  connection = EM::HttpRequest.new(uri.to_s, connect_options)
126
+ # Store connection now so that close will get called if terminating or reconnecting
127
+ c = @connections[path] = {:host => host, :connection => connection, :expires_at => Time.now} if request_options[:keepalive]
126
128
  http = connection.send(verb, request_options)
127
- http.errback { fiber.resume(http.error.to_s == "Errno::ETIMEDOUT" ? 504 : 500,
128
- (http.error && http.error.to_s) || "HTTP connection failure for #{verb.to_s.upcase}") }
129
+ http.errback { @connections.delete(path); fiber.resume(*handle_error(verb, http.error)) }
129
130
  http.callback { fiber.resume(http.response_header.status, http.response, http.response_header) }
130
131
  response_code, response_body, response_headers = Fiber.yield
131
132
  response_headers = beautify_headers(response_headers) if response_headers
132
133
  result = BalancedHttpClient.response(response_code, response_body, response_headers, request_options[:head][:accept])
133
- if request_options[:keepalive]
134
- expires_at = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
135
- @connections[path] = {:host => host, :connection => connection, :expires_at => expires_at}
136
- end
134
+ c[:expires_at] = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT if request_options[:keepalive]
137
135
  [result, response_code, response_body, response_headers]
138
136
  end
139
137
 
@@ -159,9 +157,21 @@ module RightScale
159
157
  [result, code, body, headers]
160
158
  end
161
159
 
160
+ # Close all persistent connections
161
+ #
162
+ # @param [String] reason for closing
163
+ #
164
+ # @return [TrueClass] always true
165
+ def close(reason)
166
+ @connections.each_value { |c| c[:connection].close(reason) }
167
+ @connections = {}
168
+ true
169
+ end
170
+
162
171
  protected
163
172
 
164
- # Repeatedly make long-polling request until receive data or timeout
173
+ # Repeatedly make long-polling request until receive data, hit error, or timeout
174
+ # Treat "terminating" and "reconnecting" errors as an empty poll result
165
175
  #
166
176
  # @param [Symbol] verb for HTTP REST request
167
177
  # @param [EM:HttpRequest] connection to server from previous request
@@ -173,8 +183,7 @@ module RightScale
173
183
  # @raise [HttpException] HTTP failure with associated status code
174
184
  def poll_again(fiber, connection, request_options, stop_at)
175
185
  http = connection.send(:get, request_options)
176
- http.errback { fiber.resume(http.error.to_s == "Errno::ETIMEDOUT" ? 504 : 500,
177
- (http.error && http.error.to_s) || "HTTP connection failure for POLL") }
186
+ http.errback { fiber.resume(*handle_error("POLL", http.error)) }
178
187
  http.callback do
179
188
  code, body, headers = http.response_header.status, http.response, http.response_header
180
189
  if code == 200 && (body.nil? || body == "null") && Time.now < stop_at
@@ -186,6 +195,20 @@ module RightScale
186
195
  true
187
196
  end
188
197
 
198
+ # Handle error from request
199
+ #
200
+ # @param [Symbol] verb for HTTP REST request
201
+ # @param [Object] error result from HTTP connection
202
+ #
203
+ # @return [Array] status code and error message string
204
+ def handle_error(verb, error)
205
+ case error.to_s
206
+ when "terminating", "reconnecting" then [200, nil]
207
+ when "Errno::ETIMEDOUT" then [408, "Request timeout"]
208
+ else [500, (error && error.to_s) || "HTTP connection failure for #{verb.to_s.upcase}"]
209
+ end
210
+ end
211
+
189
212
  # Beautify response header keys so that in same form as RestClient
190
213
  #
191
214
  # @param [Hash] headers from response
@@ -93,8 +93,11 @@ module RightScale
93
93
  # Packet::GLOBAL, ones with no shard id
94
94
  # [Symbol] :selector for picking from qualified targets: :any or :all;
95
95
  # defaults to :any
96
- # @param [String, NilClass] token uniquely identifying this request;
97
- # defaults to randomly generated ID
96
+ #
97
+ # @option options [String] :request_uuid uniquely identifying this request; defaults to
98
+ # randomly generated
99
+ # @option options [Numeric] :time_to_live seconds before request expires and is to be ignored;
100
+ # non-positive value or nil means never expire
98
101
  #
99
102
  # @return [NilClass] always nil since there is no expected response to the request
100
103
  #
@@ -105,10 +108,10 @@ module RightScale
105
108
  # @raise [Exceptions::RetryableError] request failed but if retried may succeed
106
109
  # @raise [Exceptions::Terminating] closing client and terminating service
107
110
  # @raise [Exceptions::InternalServerError] internal error in server being accessed
108
- def push(type, payload = nil, target = nil, token = nil)
111
+ def push(type, payload = nil, target = nil, options = {})
109
112
  raise RuntimeError, "#{self.class.name}#init was not called" unless @auth
110
113
  client = (@api && @api.support?(type)) ? @api : @router
111
- client.push(type, payload, target, token)
114
+ client.push(type, payload, target, options)
112
115
  end
113
116
 
114
117
  # Route a request to a single target with a response expected
@@ -127,8 +130,11 @@ module RightScale
127
130
  # [Array] :tags that must all be associated with a target for it to be selected
128
131
  # [Hash] :scope for restricting routing which may contain:
129
132
  # [Integer] :account id that agents must be associated with to be included
130
- # @param [String, NilClass] token uniquely identifying this request;
131
- # defaults to randomly generated ID
133
+ #
134
+ # @option options [String] :request_uuid uniquely identifying this request; defaults to
135
+ # randomly generated
136
+ # @option options [Numeric] :time_to_live seconds before request expires and is to be ignored;
137
+ # non-positive value or nil means never expire
132
138
  #
133
139
  # @return [Result, NilClass] response from request
134
140
  #
@@ -139,10 +145,10 @@ module RightScale
139
145
  # @raise [Exceptions::RetryableError] request failed but if retried may succeed
140
146
  # @raise [Exceptions::Terminating] closing client and terminating service
141
147
  # @raise [Exceptions::InternalServerError] internal error in server being accessed
142
- def request(type, payload = nil, target = nil, token = nil)
148
+ def request(type, payload = nil, target = nil, options = {})
143
149
  raise RuntimeError, "#{self.class.name}#init was not called" unless @auth
144
150
  client = (@api && @api.support?(type)) ? @api : @router
145
- client.request(type, payload, target, token)
151
+ client.request(type, payload, target, options)
146
152
  end
147
153
 
148
154
  # Route event
@@ -54,7 +54,7 @@ module RightScale
54
54
  RECONNECT_INTERVAL = 2
55
55
 
56
56
  # Maximum interval between attempts to reconnect or long-poll when router is not responding
57
- MAX_RECONNECT_INTERVAL = 60
57
+ MAX_RECONNECT_INTERVAL = 30
58
58
 
59
59
  # Interval between checks for lost WebSocket connection
60
60
  CHECK_INTERVAL = 5
@@ -116,8 +116,11 @@ module RightScale
116
116
  # Packet::GLOBAL, ones with no shard id
117
117
  # [Symbol] :selector for picking from qualified targets: :any or :all;
118
118
  # defaults to :any
119
- # @param [String, NilClass] token uniquely identifying this request;
120
- # defaults to randomly generated ID
119
+ #
120
+ # @option options [String] :request_uuid uniquely identifying this request; defaults to
121
+ # randomly generated
122
+ # @option options [Numeric] :time_to_live seconds before request expires and is to be ignored;
123
+ # non-positive value or nil means never expire
121
124
  #
122
125
  # @return [NilClass] always nil since there is no expected response to the request
123
126
  #
@@ -127,12 +130,12 @@ module RightScale
127
130
  # @raise [Exceptions::RetryableError] request failed but if retried may succeed
128
131
  # @raise [Exceptions::Terminating] closing client and terminating service
129
132
  # @raise [Exceptions::InternalServerError] internal error in server being accessed
130
- def push(type, payload, target, token = nil)
133
+ def push(type, payload, target, options = {})
131
134
  params = {
132
135
  :type => type,
133
136
  :payload => payload,
134
137
  :target => target }
135
- make_request(:post, "/push", params, type.split("/")[2], token)
138
+ make_request(:post, "/push", params, type.split("/")[2], options)
136
139
  end
137
140
 
138
141
  # Route a request to a single target with a response expected
@@ -152,8 +155,11 @@ module RightScale
152
155
  # [Array] :tags that must all be associated with a target for it to be selected
153
156
  # [Hash] :scope for restricting routing which may contain:
154
157
  # [Integer] :account id that agents must be associated with to be included
155
- # @param [String, NilClass] token uniquely identifying this request;
156
- # defaults to randomly generated ID
158
+ #
159
+ # @option options [String] :request_uuid uniquely identifying this request; defaults to
160
+ # randomly generated
161
+ # @option options [Numeric] :time_to_live seconds before request expires and is to be ignored;
162
+ # non-positive value or nil means never expire
157
163
  #
158
164
  # @return [Result, NilClass] response from request
159
165
  #
@@ -163,12 +169,12 @@ module RightScale
163
169
  # @raise [Exceptions::RetryableError] request failed but if retried may succeed
164
170
  # @raise [Exceptions::Terminating] closing client and terminating service
165
171
  # @raise [Exceptions::InternalServerError] internal error in server being accessed
166
- def request(type, payload, target, token = nil)
172
+ def request(type, payload, target, options = {})
167
173
  params = {
168
174
  :type => type,
169
175
  :payload => payload,
170
176
  :target => target }
171
- make_request(:post, "/request", params, type.split("/")[2], token)
177
+ make_request(:post, "/request", params, type.split("/")[2], options)
172
178
  end
173
179
 
174
180
  # Route event
@@ -198,7 +204,7 @@ module RightScale
198
204
  Log.info("Sending EVENT <#{event[:uuid]}> #{event[:type]}#{path}#{to}")
199
205
  @websocket.send(JSON.dump(params))
200
206
  else
201
- make_request(:post, "/notify", params, "notify", event[:uuid], :filter_params => ["event"])
207
+ make_request(:post, "/notify", params, "notify", :request_uuid => event[:uuid], :filter_params => ["event"])
202
208
  end
203
209
  true
204
210
  end
@@ -344,11 +350,32 @@ module RightScale
344
350
  @listen_interval = CHECK_INTERVAL
345
351
  end
346
352
 
347
- # Loop using next_tick or timer
353
+ listen_loop_wait(Time.now, @listen_interval, routing_keys, &handler)
354
+ end
355
+
356
+ # Wait specified interval before next listen loop
357
+ # Continue waiting if interval changes while waiting
358
+ #
359
+ # @param [Time] started_at time when first started waiting
360
+ # @param [Numeric] interval to wait
361
+ # @param [Array, NilClass] routing_keys for event sources of interest with nil meaning all
362
+ #
363
+ # @yield [event] required block called each time event received
364
+ # @yieldparam [Hash] event received
365
+ #
366
+ # @return [TrueClass] always true
367
+ def listen_loop_wait(started_at, interval, routing_keys, &handler)
348
368
  if @listen_interval == 0
349
369
  EM_S.next_tick { listen_loop(routing_keys, &handler) }
350
370
  else
351
- @listen_timer = EM_S::Timer.new(@listen_interval) { listen_loop(routing_keys, &handler) }
371
+ @listen_timer = EM_S::Timer.new(interval) do
372
+ remaining = @listen_interval - (Time.now - started_at)
373
+ if remaining > 0
374
+ listen_loop_wait(started_at, remaining, routing_keys, &handler)
375
+ else
376
+ listen_loop(routing_keys, &handler)
377
+ end
378
+ end
352
379
  end
353
380
  true
354
381
  end
@@ -555,7 +582,7 @@ module RightScale
555
582
  :poll_timeout => @options[:listen_timeout] }
556
583
 
557
584
  event_uuids = []
558
- events = make_request(:poll, "/listen", params, "listen", nil, options)
585
+ events = make_request(:poll, "/listen", params, "listen", options)
559
586
  if events
560
587
  events.each do |event|
561
588
  event = SerializationHelper.symbolize_keys(event)
@@ -610,7 +637,7 @@ module RightScale
610
637
  #
611
638
  # @return [Boolean] true if router not responding, otherwise false
612
639
  def router_not_responding?
613
- @close_code == PROTOCOL_ERROR_CLOSE && @close_reason =~ /502|503/
640
+ @close_code == PROTOCOL_ERROR_CLOSE && @close_reason =~ /408|502|503/
614
641
  end
615
642
 
616
643
  end # RouterClient
@@ -82,6 +82,7 @@ module RightScale
82
82
  @pending += 1
83
83
  command = options.dup
84
84
  command[:verbose] = verbose
85
+ command[:timeout] = timeout
85
86
  command[:cookie] = @cookie
86
87
  EM.next_tick { EM.connect('127.0.0.1', @socket_port, ConnectionHandler, command, self, response_handler) }
87
88
  EM.add_timer(timeout) { EM.stop; raise 'Timed out waiting for agent reply' } if manage_em
@@ -69,7 +69,12 @@ module RightScale
69
69
 
70
70
  # Convert RestClient exception
71
71
  def self.convert(e)
72
- e2 = create(e.http_code, e.http_body, RightScale::Response.new((e.response && e.response.headers) || {}))
72
+ e2 = if e.is_a?(RestClient::RequestTimeout)
73
+ # Special case RequestTimeout because http_code and http_body is typically nil given no actual response
74
+ create(408)
75
+ else
76
+ create(e.http_code, e.http_body, RightScale::Response.new((e.response && e.response.headers) || {}))
77
+ end
73
78
  e2.message = e.message
74
79
  e2
75
80
  end
@@ -44,12 +44,12 @@ begin
44
44
  @@win32_kill.call(sig, *pids)
45
45
  end
46
46
 
47
- # implements getpgid() for Windws
47
+ # implements getpgid() for Windows
48
48
  def self.getpgid(pid)
49
49
  # FIX: we currently only use this to check if the process is running.
50
50
  # it is possible to get the parent process id for a process in Windows if
51
51
  # we actually need this info.
52
- return Process.kill(0, pid).contains?(pid) ? 0 : -1
52
+ return Process.kill(0, pid).include?(pid) ? 0 : -1
53
53
  rescue
54
54
  raise Errno::ESRCH
55
55
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2013 RightScale Inc
2
+ # Copyright (c) 2009-2014 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -27,7 +27,7 @@ module RightScale
27
27
  class OfflineHandler
28
28
 
29
29
  # Maximum seconds to wait before starting flushing offline queue when disabling offline mode
30
- MAX_QUEUE_FLUSH_DELAY = 2 * 60
30
+ MAX_QUEUE_FLUSH_DELAY = 60
31
31
 
32
32
  # Maximum number of offline queued requests before triggering restart vote
33
33
  MAX_QUEUED_REQUESTS = 100
@@ -161,12 +161,23 @@ module RightScale
161
161
  # Queue given request in memory
162
162
  #
163
163
  # === Parameters
164
- # request(Hash):: Request to be stored
164
+ # kind(Symbol):: Kind of request: :send_push or :send_request
165
+ # type(String):: Dispatch route for the request; typically identifies actor and action
166
+ # payload(Object):: Data to be sent with marshalling en route
167
+ # target(Hash|NilClass):: Target for request
168
+ # token(String):: Token uniquely identifying request
169
+ # expires_at(Integer):: Time in seconds in Unix-epoch when this request expires and
170
+ # is to be ignored by the receiver; value 0 means never expire
171
+ #
172
+ # === Block
173
+ # Optional block used to process response asynchronously with the following parameter:
174
+ # result(Result):: Response with an OperationResult of SUCCESS, RETRY, NON_DELIVERY, or ERROR
165
175
  #
166
176
  # === Return
167
177
  # 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}
178
+ def queue_request(kind, type, payload, target, token, expires_at, &callback)
179
+ request = {:kind => kind, :type => type, :payload => payload, :target => target,
180
+ :token => token, :expires_at => expires_at, :callback => callback}
170
181
  Log.info("[offline] Queuing request: #{request.inspect}")
171
182
  vote_to_restart if (@restart_vote_count += 1) >= MAX_QUEUED_REQUESTS
172
183
  if @state == :initializing
@@ -190,7 +201,7 @@ module RightScale
190
201
 
191
202
  protected
192
203
 
193
- # Send any requests that were queued while in offline mode
204
+ # Send any requests that were queued while in offline mode and have not yet timed out
194
205
  # Do this asynchronously to allow for agents to respond to requests
195
206
  # Once all in-memory requests have been flushed, switch off offline mode
196
207
  #
@@ -204,10 +215,12 @@ module RightScale
204
215
  Log.info("[offline] Starting to flush request queue of size #{@queue.size}") unless again || @mode == :initializing
205
216
  if @queue.any?
206
217
  r = @queue.shift
207
- if r[:callback]
208
- Sender.instance.send(r[:kind], r[:type], r[:payload], r[:target]) { |result| r[:callback].call(result) }
218
+ options = {:token => r[:token]}
219
+ if r[:expires_at] != 0 && (options[:time_to_live] = r[:expires_at] - Time.now.to_i) <= 0
220
+ Log.info("[offline] Dropping queued request <#{r[:token]}> because it expired " +
221
+ "#{(-options[:time_to_live]).round} sec ago")
209
222
  else
210
- Sender.instance.send(r[:kind], r[:type], r[:payload], r[:target])
223
+ Sender.instance.send(r[:kind], r[:type], r[:payload], r[:target], options, &r[:callback])
211
224
  end
212
225
  end
213
226
  if @queue.empty?
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2009-2011 RightScale Inc
2
+ # Copyright (c) 2009-2014 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -74,13 +74,13 @@ module RightScale
74
74
  # options(Hash):: Request options
75
75
  # :targets(Array):: Target agent identities from which to randomly choose one
76
76
  # :retry_on_error(Boolean):: Whether request should be retried if recipient returned an error
77
- # :retry_delay(Fixnum):: Number of seconds delay before initial retry with -1 meaning no delay,
77
+ # :retry_delay(Numeric):: Number of seconds delay before initial retry with -1 meaning no delay,
78
78
  # defaults to DEFAULT_RETRY_DELAY
79
- # :retry_delay_count(Fixnum):: Minimum number of retries at initial :retry_delay value before
79
+ # :retry_delay_count(Numeric):: Minimum number of retries at initial :retry_delay value before
80
80
  # increasing delay exponentially and decreasing this count exponentially, defaults to
81
81
  # DEFAULT_RETRY_DELAY_COUNT
82
- # :max_retry_delay(Fixnum):: Maximum number of seconds of retry delay, defaults to DEFAULT_MAX_RETRY_DELAY
83
- # :timeout(Fixnum):: Number of seconds with no response before error callback gets called, with
82
+ # :max_retry_delay(Numeric):: Maximum number of seconds of retry delay, defaults to DEFAULT_MAX_RETRY_DELAY
83
+ # :timeout(Numeric):: Number of seconds with no response before error callback gets called, with
84
84
  # -1 meaning never, defaults to DEFAULT_TIMEOUT
85
85
  #
86
86
  # === Raises
@@ -90,6 +90,7 @@ module RightScale
90
90
  raise ArgumentError.new("payload is required") unless (@payload = payload)
91
91
  @retry_on_error = options[:retry_on_error]
92
92
  @timeout = options[:timeout] || DEFAULT_TIMEOUT
93
+ @expires_at = Time.now.to_i + @timeout if @timeout > 0
93
94
  @retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
94
95
  @retry_delay_count = options[:retry_delay_count] || DEFAULT_RETRY_DELAY_COUNT
95
96
  @max_retry_delay = options[:max_retry_delay] || DEFAULT_MAX_RETRY_DELAY
@@ -105,13 +106,18 @@ module RightScale
105
106
  # === Return
106
107
  # true:: Always return true
107
108
  def run
108
- Sender.instance.send_request(@operation, @payload, retrieve_target(@targets)) { |r| handle_response(r) }
109
- if @cancel_timer.nil? && @timeout > 0
110
- @cancel_timer = EM::Timer.new(@timeout) do
111
- msg = "Request #{@operation} timed out after #{@timeout} seconds"
112
- Log.info(msg)
113
- cancel(msg)
114
- end
109
+ cancel = Proc.new do
110
+ msg = "Request #{@operation} timed out after #{@timeout} seconds"
111
+ Log.info(msg)
112
+ cancel(msg)
113
+ end
114
+
115
+ options = {}
116
+ if @expires_at.nil? || (options[:time_to_live] = @expires_at - Time.now.to_i) > 0
117
+ Sender.instance.send_request(@operation, @payload, retrieve_target(@targets), options) { |r| handle_response(r) }
118
+ @cancel_timer = EM::Timer.new(@timeout) { cancel.call } if @cancel_timer.nil? && @timeout > 0
119
+ else
120
+ cancel.call
115
121
  end
116
122
  true
117
123
  end
@@ -404,9 +404,15 @@ module RightScale
404
404
  pgid = Process.getpgid(pid) rescue -1
405
405
  name = human_readable_name(agent_name, pid_file.identity)
406
406
  if pgid != -1
407
- psdata = `ps up #{pid}`.split("\n").last.split
408
- memory = (psdata[5].to_i / 1024)
409
- puts "#{name} is alive, using #{memory}MB of memory"
407
+ message = "#{name} is alive"
408
+ unless RightScale::Platform.windows?
409
+ # Windows Platform code currently does not support retrieving memory usage
410
+ # information for another process, so only include it for linux
411
+ psdata = `ps up #{pid}`.split("\n").last.split
412
+ memory = (psdata[5].to_i / 1024)
413
+ message << ", using #{memory}MB of memory"
414
+ end
415
+ puts message
410
416
  res = true
411
417
  else
412
418
  puts "#{name} is not running but has a stale pid file at #{pid_file}"