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