right_agent 2.0.8-x86-mingw32 → 2.1.0-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/right_agent.rb +1 -2
- data/lib/right_agent/actors/agent_manager.rb +3 -3
- data/lib/right_agent/agent.rb +14 -4
- data/lib/right_agent/clients.rb +10 -0
- data/lib/right_agent/clients/balanced_http_client.rb +210 -122
- data/lib/right_agent/clients/base_retry_client.rb +25 -6
- data/lib/right_agent/clients/blocking_client.rb +155 -0
- data/lib/right_agent/clients/non_blocking_client.rb +198 -0
- data/lib/right_agent/clients/right_http_client.rb +2 -2
- data/lib/right_agent/clients/router_client.rb +205 -80
- data/lib/right_agent/connectivity_checker.rb +1 -1
- data/lib/right_agent/eventmachine_spawn.rb +70 -0
- data/lib/right_agent/exceptions.rb +4 -20
- data/lib/right_agent/http_exceptions.rb +87 -0
- data/lib/right_agent/minimal.rb +1 -0
- data/lib/right_agent/retryable_request.rb +1 -1
- data/lib/right_agent/scripts/agent_controller.rb +12 -6
- data/lib/right_agent/scripts/agent_deployer.rb +6 -0
- data/lib/right_agent/sender.rb +31 -6
- data/right_agent.gemspec +2 -2
- data/spec/agent_spec.rb +9 -6
- data/spec/clients/balanced_http_client_spec.rb +349 -244
- data/spec/clients/base_retry_client_spec.rb +31 -15
- data/spec/clients/blocking_client_spec.rb +265 -0
- data/spec/clients/non_blocking_client_spec.rb +387 -0
- data/spec/clients/router_client_spec.rb +390 -171
- data/spec/http_exceptions_spec.rb +106 -0
- data/spec/retryable_request_spec.rb +13 -13
- data/spec/sender_spec.rb +48 -22
- data/spec/spec_helper.rb +0 -26
- metadata +10 -3
data/lib/right_agent.rb
CHANGED
@@ -27,14 +27,13 @@ require 'json'
|
|
27
27
|
require 'openssl'
|
28
28
|
require 'right_amqp'
|
29
29
|
|
30
|
-
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'monkey_patches'))
|
31
|
-
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'protocol_version_mixin'))
|
32
30
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'payload_formatter'))
|
33
31
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'packets'))
|
34
32
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'enrollment_result'))
|
35
33
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'console'))
|
36
34
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'daemonize'))
|
37
35
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'exceptions'))
|
36
|
+
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'http_exceptions'))
|
38
37
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'multiplexer'))
|
39
38
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'tracer'))
|
40
39
|
require File.normalize_path(File.join(RIGHT_AGENT_BASE_DIR, 'audit_formatter'))
|
@@ -162,7 +162,7 @@ class AgentManager
|
|
162
162
|
options = RightScale::SerializationHelper.symbolize_keys(options)
|
163
163
|
res = success_result
|
164
164
|
begin
|
165
|
-
if error = @agent.connect(options[:host], options[:port], options[:id], options[:priority], options[:force])
|
165
|
+
if (error = @agent.connect(options[:host], options[:port], options[:id], options[:priority], options[:force]))
|
166
166
|
res = error_result(error)
|
167
167
|
end
|
168
168
|
rescue Exception => e
|
@@ -185,7 +185,7 @@ class AgentManager
|
|
185
185
|
options = RightScale::SerializationHelper.symbolize_keys(options)
|
186
186
|
res = success_result
|
187
187
|
begin
|
188
|
-
if error = @agent.disconnect(options[:host], options[:port], options[:remove])
|
188
|
+
if (error = @agent.disconnect(options[:host], options[:port], options[:remove]))
|
189
189
|
res = error_result(error)
|
190
190
|
end
|
191
191
|
rescue Exception => e
|
@@ -206,7 +206,7 @@ class AgentManager
|
|
206
206
|
options = RightScale::SerializationHelper.symbolize_keys(options)
|
207
207
|
res = success_result
|
208
208
|
begin
|
209
|
-
if error = @agent.connect_failed(options[:brokers])
|
209
|
+
if (error = @agent.connect_failed(options[:brokers]))
|
210
210
|
res = error_result(error)
|
211
211
|
end
|
212
212
|
rescue Exception => e
|
data/lib/right_agent/agent.rb
CHANGED
@@ -138,6 +138,8 @@ module RightScale
|
|
138
138
|
# exceeding MAX_QUEUED_REQUESTS or by repeated failures to access RightNet when online (no argument)
|
139
139
|
# :services(Symbol):: List of services provided by this agent; defaults to all methods exposed by actors
|
140
140
|
# :secure(Boolean):: true indicates to use security features of RabbitMQ to restrict agents to themselves
|
141
|
+
# :fiber_pool_size(Integer):: Size of fiber pool
|
142
|
+
# :fiber_pool(FiberPool):: Fiber pool configured for use with EventMachine when making HTTP requests
|
141
143
|
# :mode(Symbol):: RightNet communication mode: :http or :amqp; defaults to :amqp
|
142
144
|
# :api_url(String):: Domain name for HTTP access to RightApi server
|
143
145
|
# :account_id(Integer):: Identifier for account owning this agent
|
@@ -218,7 +220,7 @@ module RightScale
|
|
218
220
|
# Need to give EM (on Windows) a chance to respond to the AMQP handshake
|
219
221
|
# before doing anything interesting to prevent AMQP handshake from
|
220
222
|
# timing-out; delay post-connected activity a second
|
221
|
-
|
223
|
+
EM_S.add_timer(1) { start_service }
|
222
224
|
elsif status == :failed
|
223
225
|
terminate("failed to connect to any brokers during startup")
|
224
226
|
elsif status == :timeout
|
@@ -575,6 +577,7 @@ module RightScale
|
|
575
577
|
end
|
576
578
|
|
577
579
|
@options[:async_response] = true unless @options.has_key?(:async_response)
|
580
|
+
@options[:non_blocking] = true if @options[:fiber_pool_size].to_i > 0
|
578
581
|
|
579
582
|
@identity = @options[:identity]
|
580
583
|
parsed_identity = AgentIdentity.parse(@identity)
|
@@ -613,7 +616,7 @@ module RightScale
|
|
613
616
|
@history.update("run")
|
614
617
|
start_console if @options[:console] && !@options[:daemonize]
|
615
618
|
EM.next_tick { @options[:ready_callback].call } if @options[:ready_callback]
|
616
|
-
|
619
|
+
@client.listen(nil) { |e| handle_event(e) } if @mode == :http
|
617
620
|
|
618
621
|
# Need to keep reconnect interval at least :connect_timeout in size,
|
619
622
|
# otherwise connection_status callback will not timeout prior to next
|
@@ -640,15 +643,22 @@ module RightScale
|
|
640
643
|
if ["Push", "Request"].include?(event[:type])
|
641
644
|
# Use next_tick to ensure that on main reactor thread
|
642
645
|
# so that any data access is thread safe
|
643
|
-
|
646
|
+
EM_S.next_tick do
|
644
647
|
begin
|
645
648
|
if (result = @dispatcher.dispatch(event_to_packet(event))) && event[:type] == "Request"
|
646
649
|
@client.notify(result_to_event(result), [result.to])
|
647
650
|
end
|
651
|
+
rescue Dispatcher::DuplicateRequest
|
648
652
|
rescue Exception => e
|
649
653
|
Log.error("Failed sending response for <#{event[:uuid]}>", e, :trace)
|
650
654
|
end
|
651
655
|
end
|
656
|
+
elsif event[:type] == "Result"
|
657
|
+
if (data = event[:data]) && (result = data[:result]) && result.respond_to?(:non_delivery?) && result.non_delivery?
|
658
|
+
Log.info("Non-delivery of event <#{data[:request_uuid]}>: #{result.content}")
|
659
|
+
else
|
660
|
+
Log.error("Unexpected Result event from #{event[:from]}: #{event.inspect}")
|
661
|
+
end
|
652
662
|
else
|
653
663
|
Log.error("Unrecognized event type #{event[:type]} from #{event[:from]}")
|
654
664
|
end
|
@@ -975,7 +985,7 @@ module RightScale
|
|
975
985
|
def setup_status_checks(interval)
|
976
986
|
@check_status_count = 0
|
977
987
|
@check_status_brokers = @client.all if @mode != :http
|
978
|
-
@check_status_timer =
|
988
|
+
@check_status_timer = EM_S::PeriodicTimer.new(interval) { check_status }
|
979
989
|
true
|
980
990
|
end
|
981
991
|
|
data/lib/right_agent/clients.rb
CHANGED
@@ -23,6 +23,16 @@
|
|
23
23
|
|
24
24
|
CLIENTS_BASE_DIR = File.join(File.dirname(__FILE__), 'clients')
|
25
25
|
|
26
|
+
unless defined?(Fiber)
|
27
|
+
# To avoid load errors when using pre-1.9 ruby
|
28
|
+
class Fiber
|
29
|
+
def self.current; nil end
|
30
|
+
def self.yield; [] end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'non_blocking_client'))
|
35
|
+
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'blocking_client'))
|
26
36
|
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'balanced_http_client'))
|
27
37
|
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'base_retry_client'))
|
28
38
|
require File.normalize_path(File.join(CLIENTS_BASE_DIR, 'auth_client'))
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2013 RightScale Inc
|
2
|
+
# Copyright (c) 2013-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
|
@@ -21,12 +21,15 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
-
require 'restclient'
|
25
|
-
|
26
24
|
module RightScale
|
27
25
|
|
28
|
-
# HTTP REST client for request
|
29
|
-
#
|
26
|
+
# HTTP REST client for request-balanced access to RightScale servers
|
27
|
+
# Requests can be made using the EventMachine asynchronous HTTP interface
|
28
|
+
# in an efficient i/o non-blocking fashion using fibers or they can be made
|
29
|
+
# using the RestClient interface; either way they are synchronous to the client
|
30
|
+
# For the non-blocking i/o approach this class must be used from a spawned fiber
|
31
|
+
# rather than the root fiber
|
32
|
+
# This class is intended for use by instance agents and by infrastructure servers
|
30
33
|
# and therefore supports both session cookie and global session-based authentication
|
31
34
|
class BalancedHttpClient
|
32
35
|
|
@@ -41,12 +44,15 @@ module RightScale
|
|
41
44
|
# Default time for HTTP connection to open
|
42
45
|
DEFAULT_OPEN_TIMEOUT = 2
|
43
46
|
|
44
|
-
#
|
47
|
+
# Time to wait for health check response
|
45
48
|
HEALTH_CHECK_TIMEOUT = 5
|
46
49
|
|
47
50
|
# Default time to wait for response from request
|
48
51
|
DEFAULT_REQUEST_TIMEOUT = 30
|
49
52
|
|
53
|
+
# Maximum time between uses of an HTTP connection
|
54
|
+
CONNECTION_REUSE_TIMEOUT = 5
|
55
|
+
|
50
56
|
# Default health check path
|
51
57
|
DEFAULT_HEALTH_CHECK_PATH = "/health-check"
|
52
58
|
|
@@ -66,38 +72,41 @@ module RightScale
|
|
66
72
|
# defaults to DEFAULT_HEALTH_CHECK_PATH
|
67
73
|
# @option options [Array] :filter_params symbols or strings for names of request parameters
|
68
74
|
# whose values are to be hidden when logging; can be augmented on individual requests
|
75
|
+
# @option options [Boolean] :non_blocking i/o is to be used for HTTP requests by applying
|
76
|
+
# EM::HttpRequest and fibers instead of RestClient; requests remain synchronous
|
69
77
|
def initialize(urls, options = {})
|
70
78
|
@urls = split(urls)
|
71
79
|
@api_version = options[:api_version]
|
72
80
|
@server_name = options[:server_name]
|
73
81
|
@filter_params = (options[:filter_params] || []).map { |p| p.to_s }
|
74
82
|
|
75
|
-
# Create
|
76
|
-
|
77
|
-
@health_check_proc = Proc.new do |host|
|
78
|
-
uri = URI.parse(host)
|
79
|
-
uri.user = uri.password = nil
|
80
|
-
uri.path = uri.path + (options[:health_check_path] || DEFAULT_HEALTH_CHECK_PATH)
|
81
|
-
check_options = {
|
82
|
-
:open_timeout => DEFAULT_OPEN_TIMEOUT,
|
83
|
-
:timeout => HEALTH_CHECK_TIMEOUT }
|
84
|
-
check_options[:headers] = {"X-API-Version" => @api_version} if @api_version
|
85
|
-
RightSupport::Net::HTTPClient.new.get(uri.to_s, check_options)
|
86
|
-
end
|
87
|
-
|
88
|
-
# Initialize use of proxy if defined
|
89
|
-
if (proxy_var = PROXY_ENVIRONMENT_VARIABLES.detect { |v| ENV.has_key?(v) })
|
90
|
-
proxy = ENV[proxy_var].match(/^[[:alpha:]]+:\/\//) ? URI.parse(ENV[proxy_var]) : URI.parse("http://" + ENV[proxy_var])
|
91
|
-
RestClient.proxy = proxy.to_s if proxy
|
92
|
-
end
|
83
|
+
# Create appropriate underlying HTTP client
|
84
|
+
@http_client = options[:non_blocking] ? NonBlockingClient.new(options) : BlockingClient.new(options)
|
93
85
|
|
94
|
-
# Initialize request balancer
|
95
|
-
balancer_options = {
|
96
|
-
:policy => RightSupport::Net::LB::HealthCheck,
|
97
|
-
:health_check => @health_check_proc }
|
86
|
+
# Initialize health check and its use in request balancer
|
87
|
+
balancer_options = {:policy => RightSupport::Net::LB::HealthCheck, :health_check => @http_client.health_check_proc }
|
98
88
|
@balancer = RightSupport::Net::RequestBalancer.new(@urls, balancer_options)
|
99
89
|
end
|
100
90
|
|
91
|
+
# Check health of server
|
92
|
+
#
|
93
|
+
# @param [String] host name of server
|
94
|
+
#
|
95
|
+
# @return [Object] health check result from server
|
96
|
+
#
|
97
|
+
# @raise [NotResponding] server is not responding
|
98
|
+
def check_health(host = nil)
|
99
|
+
begin
|
100
|
+
@http_client.health_check_proc.call(host || @urls.first)
|
101
|
+
rescue StandardError => e
|
102
|
+
if e.respond_to?(:http_code) && RETRY_STATUS_CODES.include?(e.http_code)
|
103
|
+
raise NotResponding.new("#{@server_name || host} not responding", e)
|
104
|
+
else
|
105
|
+
raise
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
101
110
|
def get(*args)
|
102
111
|
request(:get, *args)
|
103
112
|
end
|
@@ -110,25 +119,16 @@ module RightScale
|
|
110
119
|
request(:put, *args)
|
111
120
|
end
|
112
121
|
|
113
|
-
def
|
114
|
-
request(:
|
122
|
+
def poll(*args)
|
123
|
+
request(:poll, *args)
|
115
124
|
end
|
116
125
|
|
117
|
-
def
|
118
|
-
|
119
|
-
@health_check_proc.call(host || @urls.first)
|
120
|
-
rescue StandardError => e
|
121
|
-
if e.respond_to?(:http_code) && RETRY_STATUS_CODES.include?(e.http_code)
|
122
|
-
raise NotResponding.new("#{@server_name || host} not responding", e)
|
123
|
-
else
|
124
|
-
raise
|
125
|
-
end
|
126
|
-
end
|
126
|
+
def delete(*args)
|
127
|
+
request(:delete, *args)
|
127
128
|
end
|
128
129
|
|
129
|
-
|
130
|
-
|
131
|
-
# Make request via request balancer
|
130
|
+
# Make HTTP request
|
131
|
+
# If polling, continue to poll until receive data, timeout, or hit error
|
132
132
|
# Encode request parameters and response using JSON
|
133
133
|
# Apply configured authorization scheme
|
134
134
|
# Log request/response with filtered parameters included for failure or debug mode
|
@@ -144,98 +144,110 @@ module RightScale
|
|
144
144
|
# parameters whose values are to be hidden when logging in addition to the ones
|
145
145
|
# provided during object initialization
|
146
146
|
# @option options [Hash] :headers to be added to request
|
147
|
+
# @option options [Numeric] :poll_timeout maximum wait for individual poll; defaults to :request_timeout
|
147
148
|
# @option options [Symbol] :log_level to use when logging information about the request other than errors
|
148
149
|
#
|
149
150
|
# @return [Object] result returned by receiver of request
|
150
151
|
#
|
151
152
|
# @raise [NotResponding] server not responding, recommend retry
|
153
|
+
# @raise [HttpException] HTTP failure with associated status code
|
152
154
|
def request(verb, path, params = {}, options = {})
|
153
|
-
result = nil
|
154
|
-
host_picked = nil
|
155
155
|
started_at = Time.now
|
156
156
|
filter = @filter_params + (options[:filter_params] || []).map { |p| p.to_s }
|
157
|
+
log_level = options[:log_level] || Log.level
|
157
158
|
request_uuid = options[:request_uuid] || RightSupport::Data::UUID.generate
|
158
|
-
|
159
|
+
connect_options, request_options = @http_client.options(verb, path, params, request_headers(request_uuid, options), options)
|
159
160
|
|
160
|
-
Log.send(log_level, "Requesting #{verb.to_s.upcase} <#{request_uuid}> " + log_text(path, params, filter))
|
161
|
+
Log.send(log_level, "Requesting #{verb.to_s.upcase} <#{request_uuid}> " + log_text(path, params, filter, log_level))
|
161
162
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
:accept => "application/json" } }
|
169
|
-
request_options[:headers]["X-API-Version"] = @api_version if @api_version
|
170
|
-
request_options[:headers].merge!(options[:headers]) if options[:headers]
|
171
|
-
request_options[:headers]["X-DEBUG"] = true if Log.level == :debug
|
172
|
-
|
173
|
-
if [:get, :delete].include?(verb)
|
174
|
-
request_options[:query] = params if params.is_a?(Hash) && params.any?
|
175
|
-
else
|
176
|
-
request_options[:payload] = JSON.dump(params)
|
177
|
-
request_options[:headers][:content_type] = "application/json"
|
178
|
-
end
|
163
|
+
used = {}
|
164
|
+
result, code, body, headers = if verb != :poll
|
165
|
+
rest_request(verb, path, connect_options, request_options, used)
|
166
|
+
else
|
167
|
+
poll_request(path, connect_options, request_options, options[:request_timeout], started_at, used)
|
168
|
+
end
|
179
169
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
end
|
186
|
-
rescue RightSupport::Net::NoResult => e
|
187
|
-
handle_no_result(e, host_picked) do |e2|
|
188
|
-
report_failure(host_picked, path, params, filter, request_uuid, started_at, e2)
|
189
|
-
end
|
190
|
-
rescue Exception => e
|
191
|
-
report_failure(host_picked, path, params, filter, request_uuid, started_at, e)
|
192
|
-
raise
|
170
|
+
log_success(result, code, body, headers, used[:host], path, request_uuid, started_at, log_level)
|
171
|
+
result
|
172
|
+
rescue RightSupport::Net::NoResult => e
|
173
|
+
handle_no_result(e, used[:host]) do |e2|
|
174
|
+
log_failure(used[:host], path, params, filter, request_uuid, started_at, log_level, e2)
|
193
175
|
end
|
176
|
+
rescue RestClient::Exception => e
|
177
|
+
e2 = HttpExceptions.convert(e)
|
178
|
+
log_failure(used[:host], path, params, filter, request_uuid, started_at, log_level, e2)
|
179
|
+
raise e2
|
180
|
+
rescue Exception => e
|
181
|
+
log_failure(used[:host], path, params, filter, request_uuid, started_at, log_level, e)
|
182
|
+
raise
|
183
|
+
end
|
184
|
+
|
185
|
+
protected
|
194
186
|
|
195
|
-
|
187
|
+
# Construct headers for request
|
188
|
+
#
|
189
|
+
# @param [String] request_uuid uniquely identifying request
|
190
|
+
# @param [Hash] options per #request
|
191
|
+
#
|
192
|
+
# @return [Hash] headers for request
|
193
|
+
def request_headers(request_uuid, options)
|
194
|
+
headers = {"X-Request-Lineage-Uuid" => request_uuid, :accept => "application/json"}
|
195
|
+
headers["X-API-Version"] = @api_version if @api_version
|
196
|
+
headers.merge!(options[:headers]) if options[:headers]
|
197
|
+
headers["X-DEBUG"] = true if Log.level == :debug
|
198
|
+
headers
|
196
199
|
end
|
197
200
|
|
198
|
-
#
|
199
|
-
# Extract result from location header for 201 response
|
200
|
-
# JSON-decode body of other 2xx responses except for 204
|
201
|
+
# Make REST request
|
201
202
|
#
|
202
|
-
# @param [
|
203
|
-
# @param [String] host server URL where request was completed
|
203
|
+
# @param [Symbol] verb for HTTP REST request
|
204
204
|
# @param [String] path in URI for desired resource
|
205
|
-
# @param [
|
206
|
-
# @param [
|
207
|
-
# @param [
|
208
|
-
#
|
205
|
+
# @param [Hash] connect_options for HTTP connection
|
206
|
+
# @param [Hash] request_options for HTTP request
|
207
|
+
# @param [Hash] used container for returning :host used for request;
|
208
|
+
# needed so that can return it even when the request fails with an exception
|
209
209
|
#
|
210
|
-
# @return [
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
if (200..207).include?(code)
|
221
|
-
if code == 201
|
222
|
-
result = headers[:location]
|
223
|
-
elsif code == 204 || body.nil? || (body.respond_to?(:empty?) && body.empty?)
|
224
|
-
result = nil
|
225
|
-
else
|
226
|
-
result = JSON.load(body)
|
227
|
-
result = nil if result.respond_to?(:empty?) && result.empty?
|
228
|
-
end
|
229
|
-
end
|
230
|
-
length = headers[:content_length] || body.size
|
210
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
211
|
+
#
|
212
|
+
# @raise [NotResponding] server not responding, recommend retry
|
213
|
+
# @raise [HttpException] HTTP failure with associated status code
|
214
|
+
def rest_request(verb, path, connect_options, request_options, used)
|
215
|
+
result, code, body, headers = @balancer.request do |host|
|
216
|
+
uri = URI.parse(host)
|
217
|
+
uri.user = uri.password = nil
|
218
|
+
used[:host] = uri.to_s
|
219
|
+
@http_client.request(verb, path, host, connect_options, request_options)
|
231
220
|
end
|
221
|
+
[result, code, body, headers]
|
222
|
+
end
|
232
223
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
224
|
+
# Make long-polling request
|
225
|
+
#
|
226
|
+
# @param [String] path in URI for desired resource
|
227
|
+
# @param [Hash] connect_options for HTTP connection
|
228
|
+
# @param [Hash] request_options for HTTP request
|
229
|
+
# @param [Integer] request_timeout for a non-nil result
|
230
|
+
# @param [Time] started_at time for request
|
231
|
+
# @param [Hash] used container for returning :host used for request;
|
232
|
+
# needed so that can return it even when the request fails with an exception
|
233
|
+
#
|
234
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
235
|
+
#
|
236
|
+
# @raise [NotResponding] server not responding, recommend retry
|
237
|
+
# @raise [HttpException] HTTP failure with associated status code
|
238
|
+
def poll_request(path, connect_options, request_options, request_timeout, started_at, used)
|
239
|
+
result = code = body = headers = nil
|
240
|
+
if (connection = @http_client.connections[path]).nil? || Time.now >= connection[:expires_at]
|
241
|
+
# Use normal :get request using request balancer for first poll
|
242
|
+
result, code, body, headers = rest_request(:get, path, connect_options, request_options.dup, used)
|
243
|
+
return [result, code, body, headers] if (Time.now - started_at) >= request_timeout
|
244
|
+
end
|
245
|
+
if result.nil? && (connection = @http_client.connections[path]) && Time.now < connection[:expires_at]
|
246
|
+
# Continue to poll using same connection until get result, timeout, or hit error
|
247
|
+
used[:host] = connection[:host]
|
248
|
+
result, code, body, headers = @http_client.poll(connection, request_options, started_at + request_timeout)
|
249
|
+
end
|
250
|
+
[result, code, body, headers]
|
239
251
|
end
|
240
252
|
|
241
253
|
# Handle no result from balancer
|
@@ -260,7 +272,7 @@ module RightScale
|
|
260
272
|
if no_result.details.empty?
|
261
273
|
yield(no_result)
|
262
274
|
raise NotResponding.new("#{server_name} not responding", no_result)
|
263
|
-
elsif
|
275
|
+
elsif e.respond_to?(:http_code) && RETRY_STATUS_CODES.include?(e.http_code)
|
264
276
|
yield(e)
|
265
277
|
if e.http_code == 504 && (e.http_body && !e.http_body.empty?)
|
266
278
|
raise NotResponding.new(e.http_body, e)
|
@@ -274,7 +286,30 @@ module RightScale
|
|
274
286
|
true
|
275
287
|
end
|
276
288
|
|
277
|
-
#
|
289
|
+
# Log successful request completion
|
290
|
+
#
|
291
|
+
# @param [Object] result to be returned to client
|
292
|
+
# @param [Integer, NilClass] code for response status
|
293
|
+
# @param [Object] body of response
|
294
|
+
# @param [Hash] headers for response
|
295
|
+
# @param [String] host server URL where request was completed
|
296
|
+
# @param [String] path in URI for desired resource
|
297
|
+
# @param [String] request_uuid uniquely identifying request
|
298
|
+
# @param [Time] started_at time for request
|
299
|
+
# @param [Symbol] log_level to use when logging information about the request
|
300
|
+
# other than errors
|
301
|
+
#
|
302
|
+
# @return [TrueClass] always true
|
303
|
+
def log_success(result, code, body, headers, host, path, request_uuid, started_at, log_level)
|
304
|
+
length = (headers && headers[:content_length]) || (body && body.size) || "-"
|
305
|
+
duration = "%.0fms" % ((Time.now - started_at) * 1000)
|
306
|
+
completed = "Completed <#{request_uuid}> in #{duration} | #{code || "nil"} [#{host}#{path}] | #{length} bytes"
|
307
|
+
completed << " | #{result.inspect}" if log_level == :debug
|
308
|
+
Log.send(log_level, completed)
|
309
|
+
true
|
310
|
+
end
|
311
|
+
|
312
|
+
# Log request failure
|
278
313
|
# Also report it as audit entry if an instance is targeted
|
279
314
|
#
|
280
315
|
# @param [String] host server URL where request was attempted if known
|
@@ -283,13 +318,14 @@ module RightScale
|
|
283
318
|
# @param [Array] filter list of parameters whose value is to be hidden
|
284
319
|
# @param [String] request_uuid uniquely identifying request
|
285
320
|
# @param [Time] started_at time for request
|
321
|
+
# @param [Symbol] log_level to use when logging information about the request
|
286
322
|
# @param [Exception, String] exception or message that should be logged
|
287
323
|
#
|
288
324
|
# @return [TrueClass] Always return true
|
289
|
-
def
|
290
|
-
|
325
|
+
def log_failure(host, path, params, filter, request_uuid, started_at, log_level, exception)
|
326
|
+
code = exception.respond_to?(:http_code) ? exception.http_code : "nil"
|
291
327
|
duration = "%.0fms" % ((Time.now - started_at) * 1000)
|
292
|
-
Log.error("Failed <#{request_uuid}> in #{duration} | #{
|
328
|
+
Log.error("Failed <#{request_uuid}> in #{duration} | #{code} " + log_text(path, params, filter, log_level, host, exception))
|
293
329
|
true
|
294
330
|
end
|
295
331
|
|
@@ -298,12 +334,13 @@ module RightScale
|
|
298
334
|
# @param [String] path in URI for desired resource
|
299
335
|
# @param [Hash] params for HTTP request
|
300
336
|
# @param [Array, NilClass] filter augmentation to base filter list
|
337
|
+
# @param [Symbol] log_level to use when logging information about the request
|
301
338
|
# @param [String] host server URL where request was attempted if known
|
302
339
|
# @param [Exception, String, NilClass] exception or failure message that should be logged
|
303
340
|
#
|
304
341
|
# @return [String] Log text
|
305
|
-
def log_text(path, params, filter, host = nil, exception = nil)
|
306
|
-
filtered_params = (exception ||
|
342
|
+
def log_text(path, params, filter, log_level, host = nil, exception = nil)
|
343
|
+
filtered_params = (exception || log_level == :debug) ? filter(params, filter).inspect : nil
|
307
344
|
text = filtered_params ? "#{path} #{filtered_params}" : path
|
308
345
|
text = "[#{host}#{text}]" if host
|
309
346
|
text << " | #{self.class.exception_text(exception)}" if exception
|
@@ -338,6 +375,57 @@ module RightScale
|
|
338
375
|
|
339
376
|
public
|
340
377
|
|
378
|
+
# Format query parameters for inclusion in URI
|
379
|
+
# It can only handle parameters that can be converted to a string or arrays of same,
|
380
|
+
# not hashes or arrays/hashes that recursively contain arrays and/or hashes
|
381
|
+
#
|
382
|
+
# @param params [Hash] Parameters that are converted to <key>=<escaped_value> format
|
383
|
+
# and any value that is an array has each of its values formatted as <key>[]=<escaped_value>
|
384
|
+
#
|
385
|
+
# @return [String] Formatted parameter string with parameters separated by '&'
|
386
|
+
def self.format(params)
|
387
|
+
p = []
|
388
|
+
params.each do |k, v|
|
389
|
+
if v.is_a?(Array)
|
390
|
+
v.each { |v2| p << "#{k.to_s}[]=#{CGI.escape(v2.to_s)}" }
|
391
|
+
else
|
392
|
+
p << "#{k.to_s}=#{CGI.escape(v.to_s)}"
|
393
|
+
end
|
394
|
+
end
|
395
|
+
p.join("&")
|
396
|
+
end
|
397
|
+
|
398
|
+
# Process HTTP response to produce result for client
|
399
|
+
# Extract result from location header for 201 response
|
400
|
+
# JSON-decode body of other 2xx responses except for 204
|
401
|
+
# Raise exception if request failed
|
402
|
+
#
|
403
|
+
# @param [Integer] code for response status
|
404
|
+
# @param [Object] body of response
|
405
|
+
# @param [Hash] headers for response
|
406
|
+
# @param [Boolean] decode JSON-encoded body on success
|
407
|
+
#
|
408
|
+
# @return [Object] JSON-decoded response body
|
409
|
+
#
|
410
|
+
# @raise [HttpException] HTTP failure with associated status code
|
411
|
+
def self.response(code, body, headers, decode)
|
412
|
+
if (200..207).include?(code)
|
413
|
+
if code == 201
|
414
|
+
result = headers[:location]
|
415
|
+
elsif code == 204 || body.nil? || (body.respond_to?(:empty?) && body.empty?)
|
416
|
+
result = nil
|
417
|
+
elsif decode
|
418
|
+
result = JSON.load(body)
|
419
|
+
result = nil if result.respond_to?(:empty?) && result.empty?
|
420
|
+
else
|
421
|
+
result = body
|
422
|
+
end
|
423
|
+
else
|
424
|
+
raise HttpExceptions.create(code, body, headers)
|
425
|
+
end
|
426
|
+
result
|
427
|
+
end
|
428
|
+
|
341
429
|
# Extract text of exception for logging
|
342
430
|
# For RestClient exceptions extract useful info from http_body attribute
|
343
431
|
#
|
@@ -348,7 +436,7 @@ module RightScale
|
|
348
436
|
case exception
|
349
437
|
when String
|
350
438
|
exception
|
351
|
-
when RestClient::Exception
|
439
|
+
when HttpException, RestClient::Exception
|
352
440
|
if exception.http_body.nil? || exception.http_body.empty? || exception.http_body =~ /^<html>| html /
|
353
441
|
exception.message
|
354
442
|
else
|