right_agent 2.0.8-x86-mingw32 → 2.1.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.
@@ -73,6 +73,8 @@ module RightScale
73
73
  # @option options [Array] :retry_intervals between successive retries; defaults to DEFAULT_RETRY_INTERVALS
74
74
  # @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
75
75
  # @option options [Numeric] :reconnect_interval for reconnect attempts after lose connectivity
76
+ # @option options [Boolean] :non_blocking i/o is to be used for HTTP requests by applying
77
+ # EM::HttpRequest and fibers instead of RestClient; requests remain synchronous
76
78
  # @option options [Array] :filter_params symbols or strings for names of request parameters
77
79
  # whose values are to be hidden when logging; can be augmented on individual requests
78
80
  # @option options [Proc] :exception_callback for unexpected exceptions
@@ -224,15 +226,16 @@ module RightScale
224
226
  #
225
227
  # @return [TrueClass] always true
226
228
  #
227
- # @return [RightSupport::Net::BalancedHttpClient] client
229
+ # @return [BalancedHttpClient] client
228
230
  def create_http_client
229
231
  url = @auth_client.send(@type.to_s + "_url")
230
232
  Log.info("Connecting to #{@options[:server_name]} via #{url.inspect}")
231
233
  options = {
232
234
  :server_name => @options[:server_name],
233
235
  :open_timeout => @options[:open_timeout],
234
- :request_timeout => @options[:request_timeout] }
236
+ :request_timeout => @options[:request_timeout], }
235
237
  options[:api_version] = @options[:api_version] if @options[:api_version]
238
+ options[:non_blocking] = @options[:non_blocking] if @options[:non_blocking]
236
239
  options[:filter_params] = @options[:filter_params] if @options[:filter_params]
237
240
  @http_client = RightScale::BalancedHttpClient.new(url, options)
238
241
  end
@@ -271,7 +274,7 @@ module RightScale
271
274
  unless @reconnecting
272
275
  @reconnecting = true
273
276
  @stats["reconnects"].update("initiate")
274
- @reconnect_timer = EM::PeriodicTimer.new(rand(@options[:reconnect_interval])) do
277
+ @reconnect_timer = EM_S::PeriodicTimer.new(rand(@options[:reconnect_interval])) do
275
278
  begin
276
279
  create_http_client
277
280
  if check_health == :connected
@@ -446,7 +449,7 @@ module RightScale
446
449
  if attempts == 1 && interval && (Time.now - started_at) < @options[:retry_timeout]
447
450
  Log.error("Retrying #{type} request <#{request_uuid}> in #{interval} seconds " +
448
451
  "in response to retryable error (#{retry_result.http_body})")
449
- sleep(interval)
452
+ wait(interval)
450
453
  else
451
454
  @stats["request failures"].update("#{type} - retry")
452
455
  raise Exceptions::RetryableError.new(retry_result.http_body, retry_result)
@@ -462,7 +465,7 @@ module RightScale
462
465
  # Handle not responding response by determining whether okay to retry
463
466
  # If request is being retried, this function does not return until it is time to retry
464
467
  #
465
- # @param [RightScale::BalancedHttpClient::NotResponding] not_responding exception
468
+ # @param [BalancedHttpClient::NotResponding] not_responding exception
466
469
  # indicating targeted server is too busy or out of service
467
470
  # @param [String] type of request for use in logging
468
471
  # @param [String] request_uuid originally created for this request
@@ -479,7 +482,7 @@ module RightScale
479
482
  if interval && (Time.now - started_at) < @options[:retry_timeout]
480
483
  Log.error("Retrying #{type} request <#{request_uuid}> in #{interval} seconds " +
481
484
  "in response to routing failure (#{BalancedHttpClient.exception_text(not_responding)})")
482
- sleep(interval)
485
+ wait(interval)
483
486
  else
484
487
  @stats["request failures"].update("#{type} - no result")
485
488
  self.state = :disconnected
@@ -493,6 +496,22 @@ module RightScale
493
496
  true
494
497
  end
495
498
 
499
+ # Wait the specified interval in non-blocking fashion if possible
500
+ #
501
+ # @param [Numeric] interval to wait
502
+ #
503
+ # @return [TrueClass] always true
504
+ def wait(interval)
505
+ if @options[:non_blocking]
506
+ fiber = Fiber.current
507
+ EM.add_timer(interval) { fiber.resume }
508
+ Fiber.yield
509
+ else
510
+ sleep(interval)
511
+ end
512
+ true
513
+ end
514
+
496
515
  end # BaseRetryClient
497
516
 
498
517
  end # RightScale
@@ -0,0 +1,155 @@
1
+ #--
2
+ # Copyright (c) 2013-2014 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 'restclient'
25
+
26
+ module RightScale
27
+
28
+ # Interface to HTTP using RightSupport::Net::HTTPClient and RestClient
29
+ # This interfaces blocks the given thread until an HTTP response is received
30
+ class BlockingClient
31
+
32
+ # Fully configured health check procedure for use with this client
33
+ attr_reader :health_check_proc
34
+
35
+ # Hash of active connections with request path as key and hash value containing
36
+ # :host and :expires_at
37
+ attr_reader :connections
38
+
39
+ # Initialize client
40
+ #
41
+ # @option options [String] :api_version for X-API-Version header
42
+ # @option options [String] :health_check_path in URI for health check resource;
43
+ # defaults to DEFAULT_HEALTH_CHECK_PATH
44
+ def initialize(options)
45
+ @connections = {}
46
+
47
+ # Initialize use of proxy if defined
48
+ if (v = BalancedHttpClient::PROXY_ENVIRONMENT_VARIABLES.detect { |v| ENV.has_key?(v) })
49
+ proxy_uri = ENV[v].match(/^[[:alpha:]]+:\/\//) ? URI.parse(ENV[v]) : URI.parse("http://" + ENV[v])
50
+ RestClient.proxy = proxy_uri.to_s if proxy_uri
51
+ end
52
+
53
+ # Create health check proc for use by request balancer
54
+ # Strip user and password from host name since health-check does not require authorization
55
+ @health_check_proc = Proc.new do |host|
56
+ uri = URI.parse(host)
57
+ uri.user = uri.password = nil
58
+ uri.path = uri.path + (options[:health_check_path] || BalancedHttpClient::DEFAULT_HEALTH_CHECK_PATH)
59
+ request_options = {
60
+ :open_timeout => BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
61
+ :timeout => BalancedHttpClient::HEALTH_CHECK_TIMEOUT }
62
+ request_options[:headers] = {"X-API-Version" => options[:api_version]} if options[:api_version]
63
+ request(:get, "", uri.to_s, {}, request_options)
64
+ end
65
+ end
66
+
67
+ # Construct options for HTTP request
68
+ #
69
+ # @param [Symbol] verb for HTTP REST request
70
+ # @param [String] path in URI for desired resource (ignored)
71
+ # @param [Hash] params for HTTP request
72
+ # @param [String] request_headers to be applied to request
73
+ #
74
+ # @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
75
+ # @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
76
+ # @option options [Numeric] :poll_timeout maximum wait for individual poll; defaults to :request_timeout
77
+ #
78
+ # @return [Array] connect and request option hashes
79
+ def options(verb, path, params, request_headers, options)
80
+ request_options = {
81
+ :open_timeout => options[:open_timeout] || BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
82
+ :timeout => options[:poll_timeout] || options[:request_timeout] || BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT,
83
+ :headers => request_headers }
84
+
85
+ if [:get, :delete].include?(verb)
86
+ # Doing own formatting because :query option for HTTPClient uses addressable gem
87
+ # for conversion and that gem encodes arrays in a Rails-compatible fashion without []
88
+ # markers and that is inconsistent with what sinatra expects
89
+ request_options[:query] = "?#{BalancedHttpClient.format(params)}" if params.is_a?(Hash) && params.any?
90
+ else
91
+ request_options[:payload] = JSON.dump(params)
92
+ request_options[:headers][:content_type] = "application/json"
93
+ end
94
+ [{}, request_options]
95
+ end
96
+
97
+ # Make HTTP request
98
+ #
99
+ # @param [Symbol] verb for HTTP REST request
100
+ # @param [String] path in URI for desired resource
101
+ # @param [String] host name of server
102
+ # @param [Hash] connect_options for HTTP connection (ignored)
103
+ # @param [Hash] request_options for HTTP request
104
+ #
105
+ # @return [Array] result to be returned followed by response code, body, and headers
106
+ #
107
+ # @raise [HttpException] HTTP failure with associated status code
108
+ def request(verb, path, host, connect_options, request_options)
109
+ url = host + path + request_options.delete(:query).to_s
110
+ result = request_once(verb, url, request_options)
111
+ @connections[path] = {:host => host, :path => path, :expires_at => Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT }
112
+ result
113
+ end
114
+
115
+ # Make long-polling requests until receive data or timeout
116
+ #
117
+ # @param [Hash] connection to server from previous request with keys :host, :path,
118
+ # and :expires_at, with the :expires_at being adjusted on return
119
+ # @param [Hash] request_options for HTTP request
120
+ # @param [Time] stop_at time for polling
121
+ #
122
+ # @return [Array] result to be returned followed by response code, body, and headers
123
+ #
124
+ # @raise [HttpException] HTTP failure with associated status code
125
+ def poll(connection, request_options, stop_at)
126
+ url = connection[:host] + connection[:path] + request_options.delete(:query).to_s
127
+ begin
128
+ result, code, body, headers = request_once(:get, url, request_options)
129
+ end until result || Time.now >= stop_at
130
+ connection[:expires_at] = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
131
+ [result, code, body, headers]
132
+ end
133
+
134
+ protected
135
+
136
+ # Make HTTP request once
137
+ #
138
+ # @param [Symbol] verb for HTTP REST request
139
+ # @param [String] url for request
140
+ # @param [Hash] request_options for HTTP request
141
+ #
142
+ # @return [Array] result to be returned followed by response code, body, and headers
143
+ #
144
+ # @raise [HttpException] HTTP failure with associated status code
145
+ def request_once(verb, url, request_options)
146
+ if (r = RightSupport::Net::HTTPClient.new.send(verb, url, request_options))
147
+ [BalancedHttpClient.response(r.code, r.body, r.headers, request_options[:headers][:accept]), r.code, r.body, r.headers]
148
+ else
149
+ [nil, nil, nil, nil]
150
+ end
151
+ end
152
+
153
+ end # BlockingClient
154
+
155
+ end # RightScale
@@ -0,0 +1,198 @@
1
+ #--
2
+ # Copyright (c) 2013-2014 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
+ module RightScale
25
+
26
+ # Interface to HTTP using EM::HttpRequest
27
+ # This interface uses non-blocking i/o so that HTTP requests are synchronous
28
+ # to the caller but the underlying thread yields to other activity when blocked on i/o
29
+ class NonBlockingClient
30
+
31
+ # Fully configured health check procedure for use with this client
32
+ attr_reader :health_check_proc
33
+
34
+ # Hash of active connections with request path as key and hash value containing
35
+ # :host, :connection, and :expires_at
36
+ attr_reader :connections
37
+
38
+ # Initialize client
39
+ #
40
+ # @option options [String] :api_version for X-API-Version header
41
+ # @option options [String] :health_check_path in URI for health check resource;
42
+ # defaults to BalancedHttpClient::DEFAULT_HEALTH_CHECK_PATH
43
+ def initialize(options)
44
+ # Defer requiring this gem until now so that right_agent can be used with ruby 1.8.7
45
+ require 'em-http-request'
46
+
47
+ @connections = {}
48
+
49
+ # Initialize use of proxy if defined
50
+ if (v = BalancedHttpClient::PROXY_ENVIRONMENT_VARIABLES.detect { |v| ENV.has_key?(v) })
51
+ proxy_uri = ENV[v].match(/^[[:alpha:]]+:\/\//) ? URI.parse(ENV[v]) : URI.parse("http://" + ENV[v])
52
+ @proxy = {:host => proxy_uri.host, :port => proxy_uri.port}
53
+ @proxy[:authorization] = [proxy_uri.user, proxy_uri.password] if proxy_uri.user
54
+ end
55
+
56
+ # Create health check proc for use by request balancer
57
+ # Strip user and password from host name since health-check does not require authorization
58
+ @health_check_proc = Proc.new do |host|
59
+ uri = URI.parse(host)
60
+ uri.user = uri.password = nil
61
+ uri.path = uri.path + (options[:health_check_path] || BalancedHttpClient::DEFAULT_HEALTH_CHECK_PATH)
62
+ connect_options = {
63
+ :connect_timeout => BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
64
+ :inactivity_timeout => BalancedHttpClient::HEALTH_CHECK_TIMEOUT }
65
+ connect_options[:proxy] = @proxy if @proxy
66
+ request_options = {:path => uri.path}
67
+ request_options[:head] = {"X-API-Version" => options[:api_version]} if options[:api_version]
68
+ uri.path = ""
69
+ request(:get, "", uri.to_s, connect_options, request_options)
70
+ end
71
+ end
72
+
73
+ # Construct options for HTTP request
74
+ #
75
+ # @param [Symbol] verb for HTTP REST request
76
+ # @param [String] path in URI for desired resource
77
+ # @param [Hash] params for HTTP request
78
+ # @param [String] request_headers to be applied to request
79
+ #
80
+ # @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
81
+ # @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
82
+ # @option options [Numeric] :poll_timeout maximum wait for individual poll; defaults to :request_timeout
83
+ #
84
+ # @return [Array] connect and request option hashes
85
+ def options(verb, path, params, request_headers, options)
86
+ poll_timeout = verb == :poll && options[:poll_timeout]
87
+ connect_options = {
88
+ :connect_timeout => options[:open_timeout] || BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
89
+ :inactivity_timeout => poll_timeout || options[:request_timeout] || BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT }
90
+ connect_options[:proxy] = @proxy if @proxy
91
+
92
+ request_body, request_path = if [:get, :delete].include?(verb)
93
+ # Doing own formatting because :query option on EM::HttpRequest does not reliably
94
+ # URL encode, e.g., messes up on arrays in hashes
95
+ [nil, (params.is_a?(Hash) && params.any?) ? path + "?#{BalancedHttpClient.format(params)}" : path]
96
+ else
97
+ request_headers[:content_type] = "application/json"
98
+ [(params.is_a?(Hash) && params.any?) ? JSON.dump(params) : nil, path]
99
+ end
100
+ request_options = {:path => request_path, :body => request_body, :head => request_headers}
101
+ request_options[:keepalive] = true if verb == :poll
102
+ [connect_options, request_options]
103
+ end
104
+
105
+ # Make HTTP request
106
+ # Note that the underlying thread is not blocked by the HTTP i/o, but this call itself is blocking
107
+ #
108
+ # @param [Symbol] verb for HTTP REST request
109
+ # @param [String] path in URI for desired resource
110
+ # @param [String] host name of server
111
+ # @param [Hash] connect_options for HTTP connection
112
+ # @param [Hash] request_options for HTTP request
113
+ #
114
+ # @return [Array] result to be returned followed by response code, body, and headers
115
+ #
116
+ # @raise [HttpException] HTTP failure with associated status code
117
+ def request(verb, path, host, connect_options, request_options)
118
+ # Finish forming path by stripping path, if any, from host
119
+ uri = URI.parse(host)
120
+ request_options[:path] = uri.path + request_options[:path]
121
+ uri.path = ""
122
+
123
+ # Make request an then yield fiber until it completes
124
+ fiber = Fiber.current
125
+ connection = EM::HttpRequest.new(uri.to_s, connect_options)
126
+ http = connection.send(verb, request_options)
127
+ http.errback { fiber.resume(http.error.to_s == "Errno::ETIMEDOUT" ? 504 : 500, http.error) }
128
+ http.callback { fiber.resume(http.response_header.status, http.response, http.response_header) }
129
+ response_code, response_body, response_headers = Fiber.yield
130
+ response_headers = beautify_headers(response_headers) if response_headers
131
+ result = BalancedHttpClient.response(response_code, response_body, response_headers, request_options[:head][:accept])
132
+ if request_options[:keepalive]
133
+ expires_at = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
134
+ @connections[path] = {:host => host, :connection => connection, :expires_at => expires_at}
135
+ end
136
+ [result, response_code, response_body, response_headers]
137
+ end
138
+
139
+ # Make long-polling request
140
+ # Note that the underlying thread is not blocked by the HTTP i/o, but this call itself is blocking
141
+ #
142
+ # @param [Hash] connection to server from previous request with keys :host, :connection,
143
+ # and :expires_at, with the :expires_at being adjusted on return
144
+ # @param [Hash] request_options for HTTP request
145
+ # @param [Time] stop_at time for polling
146
+ #
147
+ # @return [Array] result to be returned followed by response code, body, and headers
148
+ #
149
+ # @raise [HttpException] HTTP failure with associated status code
150
+ def poll(connection, request_options, stop_at)
151
+ uri = URI.parse(connection[:host])
152
+ request_options[:path] = uri.path + request_options[:path]
153
+ poll_again(Fiber.current, connection[:connection], request_options, stop_at)
154
+ code, body, headers = Fiber.yield
155
+ headers = beautify_headers(headers) if headers
156
+ result = BalancedHttpClient.response(code, body, headers, request_options[:head][:accept])
157
+ connection[:expires_at] = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
158
+ [result, code, body, headers]
159
+ end
160
+
161
+ protected
162
+
163
+ # Repeatedly make long-polling request until receive data or timeout
164
+ #
165
+ # @param [Symbol] verb for HTTP REST request
166
+ # @param [EM:HttpRequest] connection to server from previous request
167
+ # @param [Hash] request_options for HTTP request
168
+ # @param [Time] stop_at time for polling
169
+ #
170
+ # @return [TrueClass] always true
171
+ #
172
+ # @raise [HttpException] HTTP failure with associated status code
173
+ def poll_again(fiber, connection, request_options, stop_at)
174
+ http = connection.send(:get, request_options)
175
+ http.errback { fiber.resume(http.error.to_s == "Errno::ETIMEDOUT" ? 504 : 500, http.error) }
176
+ http.callback do
177
+ code, body, headers = http.response_header.status, http.response, http.response_header
178
+ if code == 200 && (body.nil? || body == "null") && Time.now < stop_at
179
+ poll_again(fiber, connection, request_options, stop_at)
180
+ else
181
+ fiber.resume(code, body, headers)
182
+ end
183
+ end
184
+ true
185
+ end
186
+
187
+ # Beautify response header keys so that in same form as RestClient
188
+ #
189
+ # @param [Hash] headers from response
190
+ #
191
+ # @return [Hash] response headers with keys as lower case symbols
192
+ def beautify_headers(headers)
193
+ headers.inject({}) { |out, (key, value)| out[key.gsub(/-/, '_').downcase.to_sym] = value; out }
194
+ end
195
+
196
+ end # NonBlockingClient
197
+
198
+ end # RightScale
@@ -46,6 +46,8 @@ module RightScale
46
46
  # @option options [Numeric] :retry_timeout maximum before stop retrying
47
47
  # @option options [Array] :retry_intervals between successive retries
48
48
  # @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
49
+ # @option options [Boolean] :non_blocking i/o is to be used for HTTP requests by applying
50
+ # EM::HttpRequest and fibers instead of RestClient; requests remain synchronous
49
51
  # @option options [Boolean] :long_polling_only never attempt to create a WebSocket, always long-polling instead
50
52
  # @option options [Array] :filter_params symbols or strings for names of request parameters
51
53
  # whose values are to be hidden when logging
@@ -164,8 +166,6 @@ module RightScale
164
166
  end
165
167
 
166
168
  # Receive events via an HTTP WebSocket if available, otherwise via an HTTP long-polling
167
- # This is a blocking call and therefore should be used from a thread different than
168
- # otherwise used with this object, e.g., EM.defer thread
169
169
  #
170
170
  # @param [Array, NilClass] routing_keys for event sources of interest with nil meaning all
171
171
  #