right_agent 2.0.8 → 2.1.0
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
@@ -48,32 +48,16 @@ module RightScale
|
|
48
48
|
class Terminating < RuntimeError; end
|
49
49
|
|
50
50
|
# Not authorized to make request
|
51
|
-
class Unauthorized < NestedException
|
52
|
-
def initialize(message, nested_exception = nil)
|
53
|
-
super(message, nested_exception)
|
54
|
-
end
|
55
|
-
end
|
51
|
+
class Unauthorized < NestedException; end
|
56
52
|
|
57
53
|
# Cannot connect to service, lost connection to it, or it is out of service or too busy to respond
|
58
|
-
class ConnectivityFailure < NestedException
|
59
|
-
def initialize(message, nested_exception = nil)
|
60
|
-
super(message, nested_exception)
|
61
|
-
end
|
62
|
-
end
|
54
|
+
class ConnectivityFailure < NestedException; end
|
63
55
|
|
64
56
|
# Request failed but potentially will succeed if retried
|
65
|
-
class RetryableError < NestedException
|
66
|
-
def initialize(message, nested_exception = nil)
|
67
|
-
super(message, nested_exception)
|
68
|
-
end
|
69
|
-
end
|
57
|
+
class RetryableError < NestedException; end
|
70
58
|
|
71
59
|
# Database query failed
|
72
|
-
class QueryFailure < NestedException
|
73
|
-
def initialize(message, nested_exception = nil)
|
74
|
-
super(message, nested_exception)
|
75
|
-
end
|
76
|
-
end
|
60
|
+
class QueryFailure < NestedException; end
|
77
61
|
|
78
62
|
# Error internal to specified server
|
79
63
|
class InternalServerError < NestedException
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
|
3
|
+
module RightScale
|
4
|
+
|
5
|
+
# This code is largely borrowed from RestClient
|
6
|
+
|
7
|
+
# Container for response so that access response headers in same fashion as RestClient
|
8
|
+
class Response
|
9
|
+
attr_reader :headers
|
10
|
+
|
11
|
+
def initialize(headers)
|
12
|
+
@headers = headers
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Base HTTP exception class
|
17
|
+
class HttpException < RuntimeError
|
18
|
+
attr_writer :message
|
19
|
+
attr_reader :http_code, :http_body
|
20
|
+
attr_accessor :response
|
21
|
+
|
22
|
+
def initialize(code, body, response = nil)
|
23
|
+
@http_code = code
|
24
|
+
@http_body = body
|
25
|
+
@response = response
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
"#{message}: #{http_body}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
inspect
|
34
|
+
end
|
35
|
+
|
36
|
+
def message
|
37
|
+
@message || self.class.name
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Exceptions created for each status code defined in RestClient::STATUSES
|
43
|
+
# e.g., RightScale::HttpExceptions::ResourceNotFound for status code 404
|
44
|
+
module HttpExceptions
|
45
|
+
|
46
|
+
# Exception when request failed with an error code that is not managed here
|
47
|
+
class RequestFailed < HttpException
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
message
|
51
|
+
end
|
52
|
+
|
53
|
+
def message
|
54
|
+
"HTTP status code #{http_code}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Map HTTP status codes to the corresponding exception class
|
59
|
+
HTTP_EXCEPTIONS_MAP = {}
|
60
|
+
|
61
|
+
# Create exception for given code
|
62
|
+
def self.create(code, body = "", headers = {})
|
63
|
+
if HttpExceptions::HTTP_EXCEPTIONS_MAP[code]
|
64
|
+
HttpExceptions::HTTP_EXCEPTIONS_MAP[code].new(code, body, RightScale::Response.new(headers))
|
65
|
+
else
|
66
|
+
RequestFailed.new(code, body, RightScale::Response.new(headers))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Convert RestClient exception
|
71
|
+
def self.convert(e)
|
72
|
+
e2 = create(e.http_code, e.http_body, RightScale::Response.new((e.response && e.response.headers) || {}))
|
73
|
+
e2.message = e.message
|
74
|
+
e2
|
75
|
+
end
|
76
|
+
|
77
|
+
RestClient::STATUSES.each do |code, message|
|
78
|
+
klass = Class.new(HttpException) do
|
79
|
+
send(:define_method, :message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
|
80
|
+
end
|
81
|
+
klass_constant = const_set(message.delete(' \-\''), klass)
|
82
|
+
HTTP_EXCEPTIONS_MAP[code] = klass_constant
|
83
|
+
end
|
84
|
+
|
85
|
+
end # HttpExceptions
|
86
|
+
|
87
|
+
end # RightScale
|
data/lib/right_agent/minimal.rb
CHANGED
@@ -44,3 +44,4 @@ require ::File.normalize_path('command', RIGHT_AGENT_BASE_DIR)
|
|
44
44
|
require ::File.normalize_path('log', RIGHT_AGENT_BASE_DIR)
|
45
45
|
require ::File.normalize_path('pid_file', RIGHT_AGENT_BASE_DIR)
|
46
46
|
require ::File.normalize_path('serialize/serializable', RIGHT_AGENT_BASE_DIR)
|
47
|
+
require ::File.normalize_path('eventmachine_spawn', RIGHT_AGENT_BASE_DIR)
|
@@ -88,7 +88,7 @@ module RightScale
|
|
88
88
|
def initialize(operation, payload, options = {})
|
89
89
|
raise ArgumentError.new("operation is required") unless (@operation = operation)
|
90
90
|
raise ArgumentError.new("payload is required") unless (@payload = payload)
|
91
|
-
@retry_on_error = options[:retry_on_error]
|
91
|
+
@retry_on_error = options[:retry_on_error]
|
92
92
|
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
93
93
|
@retry_delay = options[:retry_delay] || DEFAULT_RETRY_DELAY
|
94
94
|
@retry_delay_count = options[:retry_delay_count] || DEFAULT_RETRY_DELAY_COUNT
|
@@ -287,7 +287,7 @@ module RightScale
|
|
287
287
|
def dispatch(action, agent_name)
|
288
288
|
# Setup the environment from config if necessary
|
289
289
|
begin
|
290
|
-
|
290
|
+
send("#{action}_agent", agent_name)
|
291
291
|
rescue SystemExit
|
292
292
|
true
|
293
293
|
rescue SignalException
|
@@ -332,6 +332,12 @@ module RightScale
|
|
332
332
|
def start_agent(agent_name, agent_class = Agent)
|
333
333
|
puts "#{human_readable_name} being started"
|
334
334
|
|
335
|
+
if @options[:fiber_pool_size]
|
336
|
+
require 'fiber_pool'
|
337
|
+
@options[:fiber_pool] = FiberPool.new(@options[:fiber_pool_size])
|
338
|
+
EM_S.fiber_pool = @options[:fiber_pool]
|
339
|
+
end
|
340
|
+
|
335
341
|
EM.error_handler do |e|
|
336
342
|
Log.error("EM block execution failed with exception", e, :trace)
|
337
343
|
Log.error("\n\n===== Exiting due to EM block exception =====\n\n")
|
@@ -339,7 +345,7 @@ module RightScale
|
|
339
345
|
exit(1)
|
340
346
|
end
|
341
347
|
|
342
|
-
|
348
|
+
EM_S.run do
|
343
349
|
begin
|
344
350
|
@@agent = agent_class.start(@options)
|
345
351
|
rescue SystemExit
|
@@ -364,9 +370,9 @@ module RightScale
|
|
364
370
|
# (Boolean):: true if process was stopped, otherwise false
|
365
371
|
def stop_agent(agent_name)
|
366
372
|
res = false
|
367
|
-
if pid_file = AgentConfig.pid_file(agent_name)
|
373
|
+
if (pid_file = AgentConfig.pid_file(agent_name))
|
368
374
|
name = human_readable_name(agent_name, pid_file.identity)
|
369
|
-
if pid = pid_file.read_pid[:pid]
|
375
|
+
if (pid = pid_file.read_pid[:pid])
|
370
376
|
begin
|
371
377
|
Process.kill('TERM', pid)
|
372
378
|
res = true
|
@@ -405,7 +411,7 @@ module RightScale
|
|
405
411
|
else
|
406
412
|
puts "#{name} is not running but has a stale pid file at #{pid_file}"
|
407
413
|
end
|
408
|
-
elsif identity = AgentConfig.agent_options(agent_name)[:identity]
|
414
|
+
elsif (identity = AgentConfig.agent_options(agent_name)[:identity])
|
409
415
|
puts "#{human_readable_name(agent_name, identity)} is not running"
|
410
416
|
end
|
411
417
|
res
|
@@ -469,7 +475,7 @@ module RightScale
|
|
469
475
|
def configure_agent(action, options)
|
470
476
|
agent_type = options[:agent_type]
|
471
477
|
agent_name = options[:agent_name]
|
472
|
-
if agent_name != agent_type && cfg = AgentConfig.load_cfg(agent_type)
|
478
|
+
if agent_name != agent_type && (cfg = AgentConfig.load_cfg(agent_type))
|
473
479
|
base_id = (options[:base_id] || AgentIdentity.parse(cfg[:identity]).base_id.to_s).to_i
|
474
480
|
unless (identity = AgentConfig.agent_options(agent_name)[:identity]) &&
|
475
481
|
AgentIdentity.parse(identity).base_id == base_id
|
@@ -44,6 +44,7 @@
|
|
44
44
|
# --reconnect-interval SEC Set number of seconds between HTTP or AMQP reconnect attempts
|
45
45
|
# --grace-timeout SEC Set number of seconds before graceful termination times out
|
46
46
|
# --[no-]dup-check Set whether to check for and reject duplicate requests, .e.g., due to retries
|
47
|
+
# --fiber-pool-size, -f N Set size of fiber pool
|
47
48
|
# --options, -o KEY=VAL Set options that act as final override for any persisted configuration settings
|
48
49
|
# --monit Generate monit configuration file
|
49
50
|
# --test Build test deployment using default test settings
|
@@ -176,6 +177,10 @@ module RightScale
|
|
176
177
|
options[:dup_check] = b
|
177
178
|
end
|
178
179
|
|
180
|
+
opts.on('-f', '--fiber-pool-size N') do |n|
|
181
|
+
options[:fiber_pool_size] = n.to_i
|
182
|
+
end
|
183
|
+
|
179
184
|
opts.on('--prefetch COUNT') do |count|
|
180
185
|
options[:prefetch] = count.to_i
|
181
186
|
end
|
@@ -312,6 +317,7 @@ module RightScale
|
|
312
317
|
cfg[:dup_check] = options[:dup_check].nil? ? true : options[:dup_check]
|
313
318
|
cfg[:http_proxy] = options[:http_proxy] if options[:http_proxy]
|
314
319
|
cfg[:http_no_proxy] = options[:http_no_proxy] if options[:http_no_proxy]
|
320
|
+
cfg[:fiber_pool_size] = options[:fiber_pool_size] if options[:fiber_pool_size]
|
315
321
|
cfg
|
316
322
|
end
|
317
323
|
|
data/lib/right_agent/sender.rb
CHANGED
@@ -571,7 +571,9 @@ module RightScale
|
|
571
571
|
true
|
572
572
|
end
|
573
573
|
|
574
|
-
# Send request via HTTP
|
574
|
+
# Send request via HTTP
|
575
|
+
# Use next_tick for asynchronous response and to ensure
|
576
|
+
# that the request is sent using the main EM reactor thread
|
575
577
|
#
|
576
578
|
# === Parameters
|
577
579
|
# kind(Symbol):: Kind of request: :send_push or :send_request
|
@@ -583,6 +585,33 @@ module RightScale
|
|
583
585
|
# === Return
|
584
586
|
# true:: Always return true
|
585
587
|
def http_send(kind, target, packet, received_at, callback)
|
588
|
+
if @options[:async_response]
|
589
|
+
EM_S.next_tick do
|
590
|
+
begin
|
591
|
+
http_send_once(kind, target, packet, received_at, callback)
|
592
|
+
rescue Exception => e
|
593
|
+
Log.error("Failed sending or handling response for #{packet.trace} #{packet.type}", e, :trace)
|
594
|
+
@exception_stats.track("request", e)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
else
|
598
|
+
http_send_once(kind, target, packet, received_at, callback)
|
599
|
+
end
|
600
|
+
true
|
601
|
+
end
|
602
|
+
|
603
|
+
# Send request via HTTP and then immediately handle response
|
604
|
+
#
|
605
|
+
# === Parameters
|
606
|
+
# kind(Symbol):: Kind of request: :send_push or :send_request
|
607
|
+
# target(Hash|String|nil):: Target for request
|
608
|
+
# packet(Push|Request):: Request packet to send
|
609
|
+
# received_at(Time):: Time when request received
|
610
|
+
# callback(Proc|nil):: Block used to process response
|
611
|
+
#
|
612
|
+
# === Return
|
613
|
+
# true:: Always return true
|
614
|
+
def http_send_once(kind, target, packet, received_at, callback)
|
586
615
|
begin
|
587
616
|
method = packet.class.name.split("::").last.downcase
|
588
617
|
result = success_result(@agent.client.send(method, packet.type, packet.payload, target, packet.token))
|
@@ -619,11 +648,7 @@ module RightScale
|
|
619
648
|
result = Result.new(packet.token, @identity, result, from = packet.target)
|
620
649
|
result.received_at = received_at.to_f
|
621
650
|
@pending_requests[packet.token] = PendingRequest.new(kind, received_at, callback) if callback
|
622
|
-
|
623
|
-
EM.next_tick { handle_response(result) }
|
624
|
-
else
|
625
|
-
handle_response(result)
|
626
|
-
end
|
651
|
+
handle_response(result)
|
627
652
|
end
|
628
653
|
true
|
629
654
|
end
|
data/right_agent.gemspec
CHANGED
@@ -25,8 +25,8 @@ require 'rbconfig'
|
|
25
25
|
|
26
26
|
Gem::Specification.new do |spec|
|
27
27
|
spec.name = 'right_agent'
|
28
|
-
spec.version = '2.0
|
29
|
-
spec.date = '2014-
|
28
|
+
spec.version = '2.1.0'
|
29
|
+
spec.date = '2014-04-01'
|
30
30
|
spec.authors = ['Lee Kirchhoff', 'Raphael Simon', 'Tony Spataro', 'Scott Messier']
|
31
31
|
spec.email = 'lee@rightscale.com'
|
32
32
|
spec.homepage = 'https://github.com/rightscale/right_agent'
|
data/spec/agent_spec.rb
CHANGED
@@ -42,7 +42,6 @@ describe RightScale::Agent do
|
|
42
42
|
flexmock(EM).should_receive(:next_tick).and_yield
|
43
43
|
flexmock(EM).should_receive(:add_timer).and_yield
|
44
44
|
@timer = flexmock("timer")
|
45
|
-
flexmock(EM::Timer).should_receive(:new).and_return(@timer)
|
46
45
|
flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer)
|
47
46
|
@timer.should_receive(:cancel)
|
48
47
|
@broker = flexmock("broker", :subscribe => ["b1"], :publish => ["b1"], :prefetch => true,
|
@@ -249,11 +248,9 @@ describe RightScale::Agent do
|
|
249
248
|
@log = flexmock(RightScale::Log)
|
250
249
|
@log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
251
250
|
flexmock(EM).should_receive(:next_tick).and_yield
|
252
|
-
flexmock(EM).should_receive(:add_timer).and_yield
|
253
|
-
|
254
|
-
flexmock(
|
255
|
-
@timer.should_receive(:cancel).by_default
|
256
|
-
@periodic_timer = flexmock("timer")
|
251
|
+
flexmock(EM).should_receive(:add_timer).with(0, Proc).and_yield.by_default
|
252
|
+
flexmock(EM).should_receive(:add_timer).with(1, Proc).and_yield.by_default
|
253
|
+
@periodic_timer = flexmock("periodic timer")
|
257
254
|
flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@periodic_timer)
|
258
255
|
@periodic_timer.should_receive(:cancel).by_default
|
259
256
|
@broker_id = "rs-broker-123-1"
|
@@ -521,6 +518,12 @@ describe RightScale::Agent do
|
|
521
518
|
|
522
519
|
describe "Terminating" do
|
523
520
|
|
521
|
+
before(:each) do
|
522
|
+
@timer = flexmock("timer")
|
523
|
+
flexmock(EM::Timer).should_receive(:new).and_return(@timer).by_default
|
524
|
+
@timer.should_receive(:cancel).by_default
|
525
|
+
end
|
526
|
+
|
524
527
|
it "should log error for abnormal termination" do
|
525
528
|
run_in_em do
|
526
529
|
@agent = RightScale::Agent.new(:user => "me", :identity => @identity)
|
@@ -32,29 +32,37 @@ describe RightScale::BalancedHttpClient do
|
|
32
32
|
@log = flexmock(RightScale::Log)
|
33
33
|
@log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
34
34
|
@log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
35
|
+
@options = {}
|
35
36
|
@url = "http://my.com"
|
36
37
|
@urls = [@url]
|
38
|
+
@host = @url
|
37
39
|
@path = "/foo/bar"
|
38
40
|
@balancer = flexmock("balancer")
|
41
|
+
flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer).by_default
|
42
|
+
@later = (@now = Time.now)
|
43
|
+
@tick = 0.01
|
44
|
+
flexmock(Time).should_receive(:now).and_return { @later += @tick }
|
39
45
|
end
|
40
46
|
|
41
47
|
context :initialize do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
flexmock(RestClient).should_receive(:proxy=).with("https://my.proxy.com").once
|
47
|
-
RightScale::BalancedHttpClient.new(@urls)
|
48
|
-
ENV.delete(proxy)
|
49
|
-
end
|
48
|
+
it "initializes parameter filter list" do
|
49
|
+
client = RightScale::BalancedHttpClient.new(@urls)
|
50
|
+
client.instance_variable_get(:@filter_params).should == []
|
51
|
+
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
it "stores specified parameter filters" do
|
54
|
+
client = RightScale::BalancedHttpClient.new(@urls, :filter_params => [:secret])
|
55
|
+
client.instance_variable_get(:@filter_params).should == ["secret"]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "creates blocking HTTP client" do
|
59
|
+
client = RightScale::BalancedHttpClient.new(@urls)
|
60
|
+
client.instance_variable_get(:@http_client).should be_a RightScale::BlockingClient
|
61
|
+
end
|
62
|
+
|
63
|
+
it "creates non-blocking HTTP client if specified" do
|
64
|
+
client = RightScale::BalancedHttpClient.new(@urls, :non_blocking => true)
|
65
|
+
client.instance_variable_get(:@http_client).should be_a RightScale::NonBlockingClient
|
58
66
|
end
|
59
67
|
|
60
68
|
it "creates request balancer with health checker" do
|
@@ -73,24 +81,28 @@ describe RightScale::BalancedHttpClient do
|
|
73
81
|
context :check_health do
|
74
82
|
before(:each) do
|
75
83
|
@urls = ["http://my0.com", @url]
|
76
|
-
@http_client = flexmock("http client")
|
77
|
-
flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
|
78
84
|
@client = RightScale::BalancedHttpClient.new(@urls)
|
85
|
+
@health_check_proc = @client.instance_variable_get(:@http_client).health_check_proc
|
79
86
|
end
|
80
87
|
|
81
88
|
it "calls health check proc using first URL" do
|
82
|
-
@
|
89
|
+
flexmock(@health_check_proc).should_receive(:call).with("http://my0.com").once
|
83
90
|
@client.check_health
|
84
91
|
end
|
85
92
|
|
86
93
|
it "calls health check proc using specified URL" do
|
87
|
-
@
|
88
|
-
@client.check_health("http://my1.com")
|
94
|
+
flexmock(@health_check_proc).should_receive(:call).with("http://my1.com/api").once
|
95
|
+
@client.check_health("http://my1.com/api")
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns result of health check" do
|
99
|
+
flexmock(@health_check_proc).should_receive(:call).with("http://my0.com").and_return("ok").once
|
100
|
+
@client.check_health.should == "ok"
|
89
101
|
end
|
90
102
|
|
91
103
|
RightScale::BalancedHttpClient::RETRY_STATUS_CODES.each do |code|
|
92
104
|
it "raises NotResponding for #{code}" do
|
93
|
-
@
|
105
|
+
flexmock(@health_check_proc).should_receive(:call).and_raise(RightScale::HttpExceptions.create(code))
|
94
106
|
lambda { @client.check_health("http://my1.com") }.should raise_error(RightScale::BalancedHttpClient::NotResponding)
|
95
107
|
end
|
96
108
|
end
|
@@ -98,248 +110,244 @@ describe RightScale::BalancedHttpClient do
|
|
98
110
|
|
99
111
|
context :request do
|
100
112
|
before(:each) do
|
101
|
-
@
|
102
|
-
@
|
103
|
-
@
|
104
|
-
@
|
105
|
-
@response
|
106
|
-
@
|
107
|
-
@response.should_receive(:headers).and_return({:status => "200 OK"}).by_default
|
113
|
+
@params = {}
|
114
|
+
@result = {"out" => 123}
|
115
|
+
@body = JSON.dump({:out => 123})
|
116
|
+
@headers = {:status => "200 OK"}
|
117
|
+
@response = [@result, 200, @body, @headers]
|
118
|
+
@balancer.should_receive(:request).and_yield(@url).and_return(@response).by_default
|
108
119
|
flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return("random uuid").by_default
|
109
|
-
@
|
110
|
-
|
111
|
-
@http_client
|
112
|
-
flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
|
113
|
-
@client = RightScale::BalancedHttpClient.new(@urls)
|
120
|
+
@client = RightScale::BalancedHttpClient.new(@urls, :filter_params => [:secret])
|
121
|
+
@http_client = @client.instance_variable_get(:@http_client)
|
122
|
+
flexmock(@http_client).should_receive(:request).and_return(@response).by_default
|
114
123
|
end
|
115
124
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
end
|
122
|
-
|
123
|
-
it "sets default open and request timeouts" do
|
124
|
-
@options[:open_timeout].should == RightScale::BalancedHttpClient::DEFAULT_OPEN_TIMEOUT
|
125
|
-
@options[:timeout].should == RightScale::BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT
|
126
|
-
end
|
127
|
-
|
128
|
-
it "sets random request uuid in header" do
|
129
|
-
@options[:headers]["X-Request-Lineage-Uuid"] == "random uuid"
|
130
|
-
end
|
125
|
+
it "uses specified request UUID" do
|
126
|
+
@log.should_receive(:info).with("Requesting POST <my uuid> /foo/bar").once
|
127
|
+
@log.should_receive(:info).with("Completed <my uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes")
|
128
|
+
@client.request(:post, @path, @params, :request_uuid => "my uuid")
|
129
|
+
end
|
131
130
|
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
it "generates request UUID if none specified" do
|
132
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
133
|
+
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
134
|
+
@client.request(:post, @path)
|
135
|
+
end
|
135
136
|
|
136
|
-
|
137
|
-
|
138
|
-
|
137
|
+
it "logs request before sending it" do
|
138
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
139
|
+
flexmock(@http_client).should_receive(:request).and_raise(RuntimeError).once
|
140
|
+
@client.request(:post, @path) rescue nil
|
139
141
|
end
|
140
142
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
request_options = {
|
148
|
-
:open_timeout => 1,
|
149
|
-
:request_timeout => 2,
|
150
|
-
:headers => {"Authorization" => "Bearer <session>"} }
|
151
|
-
@client = RightScale::BalancedHttpClient.new(@urls, create_options)
|
152
|
-
@http_client.should_receive(:get).with("#{@url}#{@path}", on { |a| @options = a }).and_return(nil)
|
153
|
-
@client.send(:request, :get, @path, nil, request_options).should be_nil
|
154
|
-
end
|
143
|
+
it "logs using specified log level" do
|
144
|
+
@params = {:some => "data"}
|
145
|
+
@log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar {:some=>\"data\"}").once
|
146
|
+
@log.should_receive(:debug).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
|
147
|
+
@client.request(:post, @path, @params, :log_level => :debug)
|
148
|
+
end
|
155
149
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
150
|
+
it "logs using configured log level if none specified" do
|
151
|
+
@params = {:some => "data"}
|
152
|
+
@log.should_receive(:level).and_return(:debug)
|
153
|
+
@log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar {:some=>\"data\"}").once
|
154
|
+
@log.should_receive(:debug).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
|
155
|
+
@client.request(:post, @path, @params)
|
156
|
+
end
|
160
157
|
|
161
|
-
|
162
|
-
|
163
|
-
|
158
|
+
it "appends specified filter parameters to list" do
|
159
|
+
@params = {:some => "data", :secret => "data", :other => "data"}
|
160
|
+
@log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar {:some=>\"data\", :secret=>\"<hidden>\", :other=>\"<hidden>\"}").once
|
161
|
+
@log.should_receive(:debug).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
|
162
|
+
@client.request(:post, @path, @params, :log_level => :debug, :filter_params => [:other])
|
163
|
+
end
|
164
164
|
|
165
|
-
|
166
|
-
|
167
|
-
|
165
|
+
it "removes user and password from host when logging" do
|
166
|
+
flexmock(@client).should_receive(:log_success).with(@result, 200, @body, @headers, "http://my.com", @path, "random uuid", Time, :info).once
|
167
|
+
@balancer.should_receive(:request).and_yield("http://111:secret@my.com").and_return(@response).once
|
168
|
+
@client.request(:post, @path)
|
169
|
+
end
|
168
170
|
|
169
|
-
|
170
|
-
|
171
|
-
|
171
|
+
it "logs successful completion of request" do
|
172
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar")
|
173
|
+
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
174
|
+
@client.request(:post, @path, @params, @options)
|
172
175
|
end
|
173
176
|
|
174
|
-
|
175
|
-
|
176
|
-
it "uses form-encoded query option for parameters" do
|
177
|
-
params = {:id => 10}
|
178
|
-
@http_client.should_receive(verb).with("#{@url}#{@path}", on { |a| a[:query] == params &&
|
179
|
-
a[:payload].nil? }).and_return(nil)
|
180
|
-
@client.send(:request, verb, @path, params).should be_nil
|
181
|
-
end
|
182
|
-
end
|
177
|
+
it "returns result of request" do
|
178
|
+
@client.request(:post, @path, @params, @options).should == @result
|
183
179
|
end
|
184
180
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
json_payload = JSON.dump(payload)
|
190
|
-
@http_client.should_receive(verb).with("#{@url}#{@path}", on { |a| a[:payload] == json_payload &&
|
191
|
-
a[:query].nil? }).and_return(nil).once
|
192
|
-
@client.send(:request, verb, @path, payload).should be_nil
|
193
|
-
end
|
194
|
-
end
|
181
|
+
it "handles poll requests separately" do
|
182
|
+
flexmock(@client).should_receive(:poll_request).and_return(@response).once
|
183
|
+
flexmock(@client).should_receive(:rest_request).and_return(@response).never
|
184
|
+
@client.request(:poll, @path, @params, @options).should == @result
|
195
185
|
end
|
196
186
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
187
|
+
it "handles NoResult response from balancer" do
|
188
|
+
no_result = RightSupport::Net::NoResult.new("no result", {})
|
189
|
+
@log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
|
190
|
+
@balancer.should_receive(:request).and_yield(@url).once
|
191
|
+
flexmock(@http_client).should_receive(:request).and_raise(no_result).once
|
192
|
+
flexmock(@client).should_receive(:handle_no_result).with(RightSupport::Net::NoResult, @url, Proc).and_yield(no_result).once
|
193
|
+
@client.request(:get, @path)
|
194
|
+
end
|
204
195
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
196
|
+
it "converts RestClient exception to HttpException and raises it" do
|
197
|
+
bad_request = RestClient::Exceptions::EXCEPTIONS_MAP[400].new(nil, 400)
|
198
|
+
@log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
|
199
|
+
flexmock(@http_client).should_receive(:request).and_raise(bad_request).once
|
200
|
+
lambda { @client.request(:get, @path) }.should raise_error(RightScale::HttpExceptions::BadRequest)
|
201
|
+
end
|
210
202
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
end
|
203
|
+
it "logs and re-raises unexpected exception" do
|
204
|
+
@log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
|
205
|
+
flexmock(@http_client).should_receive(:request).and_raise(RuntimeError).once
|
206
|
+
lambda { @client.request(:get, @path) }.should raise_error(RuntimeError)
|
207
|
+
end
|
217
208
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
209
|
+
it "passes back host used with user and password removed" do
|
210
|
+
flexmock(@client).should_receive(:log_success).with(@result, 200, @body, @headers, "http://my.com", @path, "random uuid", Time, :info).once
|
211
|
+
@balancer.should_receive(:request).and_yield("http://111:secret@my.com").and_return(@response).once
|
212
|
+
@client.request(:post, @path)
|
213
|
+
end
|
214
|
+
end
|
223
215
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
216
|
+
context :request_headers do
|
217
|
+
before(:each) do
|
218
|
+
@request_uuid = "my uuid"
|
219
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
229
220
|
end
|
230
221
|
|
231
|
-
it "
|
232
|
-
@
|
233
|
-
|
234
|
-
@client.send(:request, :post, @path).should be_nil
|
222
|
+
it "sets request uuid in header" do
|
223
|
+
headers = @client.send(:request_headers, @request_uuid, @options)
|
224
|
+
headers["X-Request-Lineage-Uuid"].should == "my uuid"
|
235
225
|
end
|
236
226
|
|
237
|
-
it "
|
238
|
-
@
|
239
|
-
|
240
|
-
@response.should_receive(:headers).and_return({:status => "201 Created", :location => "/href"}).once
|
241
|
-
@balancer.should_receive(:request).and_return(@response).once
|
242
|
-
@client.send(:request, :get, @path).should == "/href"
|
227
|
+
it "sets response to be JSON-encoded" do
|
228
|
+
headers = @client.send(:request_headers, @request_uuid, @options)
|
229
|
+
headers[:accept] == "application/json"
|
243
230
|
end
|
244
231
|
|
245
|
-
it "
|
246
|
-
@
|
247
|
-
@
|
248
|
-
|
232
|
+
it "sets API version" do
|
233
|
+
@client = RightScale::BalancedHttpClient.new(@urls, :api_version => "2.0")
|
234
|
+
headers = @client.send(:request_headers, @request_uuid, @options)
|
235
|
+
headers["X-API-Version"].should == "2.0"
|
249
236
|
end
|
250
237
|
|
251
|
-
it "
|
252
|
-
@
|
253
|
-
|
254
|
-
@client.send(:request, :get, @path).should be_nil
|
238
|
+
it "does not set API version if not defined" do
|
239
|
+
headers = @client.send(:request_headers, @request_uuid, @options)
|
240
|
+
headers.has_key?("X-API-Version").should be false
|
255
241
|
end
|
256
242
|
|
257
|
-
it "
|
258
|
-
@
|
259
|
-
@client.send(:
|
243
|
+
it "sets any optionally specified headers" do
|
244
|
+
@options = {:headers => {"Authorization" => "Bearer <session>"}}
|
245
|
+
headers = @client.send(:request_headers, @request_uuid, @options)
|
246
|
+
headers["Authorization"].should == "Bearer <session>"
|
260
247
|
end
|
261
248
|
|
262
|
-
it "
|
263
|
-
@
|
264
|
-
@
|
265
|
-
|
249
|
+
it "sets debug option if in debug mode" do
|
250
|
+
@log.should_receive(:level).and_return(:debug)
|
251
|
+
headers = @client.send(:request_headers, @request_uuid, @options)
|
252
|
+
headers["X-DEBUG"].should be true
|
266
253
|
end
|
254
|
+
end
|
267
255
|
|
268
|
-
|
269
|
-
|
270
|
-
@
|
271
|
-
@
|
272
|
-
|
273
|
-
|
274
|
-
@
|
256
|
+
context "requesting" do
|
257
|
+
before(:each) do
|
258
|
+
@tick = 1
|
259
|
+
@used = {}
|
260
|
+
@result = {"out" => 123}
|
261
|
+
@body = JSON.dump({:out => 123})
|
262
|
+
@headers = {:status => "200 OK"}
|
263
|
+
@response = [@result, 200, @body, @headers]
|
264
|
+
@balancer.should_receive(:request).and_yield(@url).and_return(@response).by_default
|
265
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
266
|
+
@http_client = @client.instance_variable_get(:@http_client)
|
267
|
+
flexmock(@http_client).should_receive(:request).and_return(@response).by_default
|
268
|
+
@connect_options = {}
|
269
|
+
@request_options = {:open_timeout => 2, :request_timeout => 5}
|
275
270
|
end
|
276
271
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
272
|
+
context :rest_request do
|
273
|
+
it "make requests using request balancer" do
|
274
|
+
flexmock(@http_client).should_receive(:request).with(:post, @path, @url, Hash, Hash).and_return(@response).once
|
275
|
+
@balancer.should_receive(:request).and_yield(@url).once
|
276
|
+
@client.send(:rest_request, :post, @path, @connect_options, @request_options, @used)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "uses request balancer to make request" do
|
280
|
+
flexmock(@http_client).should_receive(:request).with(:post, @path, @url, Hash, Hash).and_return(@response).once
|
281
|
+
@balancer.should_receive(:request).and_yield(@url).and_return(@response).once
|
282
|
+
@client.send(:rest_request, :post, @path, @connect_options, @request_options, @used)
|
283
|
+
end
|
284
|
+
|
285
|
+
it "passes back host used with user and password removed" do
|
286
|
+
@balancer.should_receive(:request).and_yield("http://111:secret@my.com").and_return(@response).once
|
287
|
+
@client.send(:rest_request, :post, @path, @connect_options, @request_options, @used)
|
288
|
+
@used[:host].should == "http://my.com"
|
289
|
+
end
|
290
|
+
|
291
|
+
it "returns result and response info" do
|
292
|
+
@balancer.should_receive(:request).and_yield(@url).and_return(@response).once
|
293
|
+
@client.send(:rest_request, :post, @path, @connect_options, @request_options, @used).should == @response
|
294
|
+
end
|
281
295
|
end
|
282
296
|
|
283
|
-
context
|
297
|
+
context :poll_request do
|
284
298
|
before(:each) do
|
285
|
-
|
286
|
-
|
299
|
+
@started_at = @now
|
300
|
+
@request_timeout = 30
|
301
|
+
@stop_at = @started_at + @request_timeout
|
302
|
+
@connection = {:host => @host, :expires_at => @later + 5}
|
287
303
|
end
|
288
304
|
|
289
|
-
it "
|
290
|
-
@http_client.
|
291
|
-
@
|
292
|
-
@
|
293
|
-
@client.send(:request, :post, @path)
|
305
|
+
it "makes get request if there is no existing connection" do
|
306
|
+
@http_client.instance_variable_get(:@connections)[@path].should be nil
|
307
|
+
flexmock(@http_client).should_receive(:request).with(:get, @path, @url, Hash, Hash).and_return(@response).once
|
308
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, @request_timeout, @started_at, @used)
|
294
309
|
end
|
295
310
|
|
296
|
-
it "
|
297
|
-
@
|
298
|
-
@
|
299
|
-
@
|
300
|
-
@client.send(:
|
311
|
+
it "makes get request if there is connection but it has expired" do
|
312
|
+
@connection = {:host => @host, :expires_at => @now}
|
313
|
+
@http_client.instance_variable_get(:@connections)[@path] = @connection
|
314
|
+
flexmock(@http_client).should_receive(:request).with(:get, @path, @url, Hash, Hash).and_return(@response).once
|
315
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, @request_timeout, @started_at, @used)
|
301
316
|
end
|
302
317
|
|
303
|
-
it "
|
304
|
-
@response
|
305
|
-
@
|
306
|
-
@
|
307
|
-
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 99 bytes").once
|
308
|
-
@client.send(:request, :post, @path)
|
318
|
+
it "returns after get request if request has timed out" do
|
319
|
+
@response = [nil, 200, nil, @headers]
|
320
|
+
@balancer.should_receive(:request).and_return(@response).once
|
321
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, 1, @started_at, @used).should == @response
|
309
322
|
end
|
310
323
|
|
311
|
-
it "
|
312
|
-
|
313
|
-
@
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
@
|
318
|
-
@client.send(:
|
324
|
+
it "makes poll request after get request if result is nil and there is an unexpired connection" do
|
325
|
+
get_response = [nil, 200, nil, @headers]
|
326
|
+
@balancer.should_receive(:request).and_return do
|
327
|
+
@http_client.instance_variable_get(:@connections)[@path] = @connection
|
328
|
+
get_response
|
329
|
+
end.once
|
330
|
+
flexmock(@http_client).should_receive(:poll).with(@connection, @request_options, @stop_at).and_return(@response).once
|
331
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, @request_timeout, @started_at, @used).should == @response
|
319
332
|
end
|
320
333
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
334
|
+
it "makes poll request without get request if there is an unexpired connection" do
|
335
|
+
@connection[:expires_at] = @later + 10
|
336
|
+
@http_client.instance_variable_get(:@connections)[@path] = @connection
|
337
|
+
flexmock(@http_client).should_receive(:poll).with(@connection, @request_options, @stop_at).and_return(@response).once
|
338
|
+
flexmock(@http_client).should_receive(:request).never
|
339
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, @request_timeout, @started_at, @used).should == @response
|
340
|
+
end
|
328
341
|
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
|
334
|
-
@client.send(:request, :post, @path, @params, @options)
|
335
|
-
end
|
342
|
+
it "returns result and response info" do
|
343
|
+
@balancer.should_receive(:request).and_yield(@url).and_return(@response).once
|
344
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, @request_timeout, @started_at, @used).should == @response
|
345
|
+
end
|
336
346
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
lambda { @client.send(:request, :post, @path, @params, @options) }.should raise_error(RuntimeError)
|
342
|
-
end
|
347
|
+
it "passes back host used" do
|
348
|
+
@balancer.should_receive(:request).and_yield("http://111:secret@my.com").and_return(@response).once
|
349
|
+
@client.send(:poll_request, @path, @connect_options, @request_options, @request_timeout, @started_at, @used)
|
350
|
+
@used[:host].should == "http://my.com"
|
343
351
|
end
|
344
352
|
end
|
345
353
|
end
|
@@ -364,8 +372,8 @@ describe RightScale::BalancedHttpClient do
|
|
364
372
|
end
|
365
373
|
|
366
374
|
it "uses last exception stored in NoResult details" do
|
367
|
-
gateway_timeout =
|
368
|
-
bad_request =
|
375
|
+
gateway_timeout = RightScale::HttpExceptions.create(504, "server timeout")
|
376
|
+
bad_request = RightScale::HttpExceptions.create(400, "bad data")
|
369
377
|
@no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout, @url => bad_request})
|
370
378
|
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
|
371
379
|
@yielded.should == bad_request
|
@@ -378,7 +386,7 @@ describe RightScale::BalancedHttpClient do
|
|
378
386
|
end
|
379
387
|
|
380
388
|
it "uses http_body in raised NotResponding exception if status code is 504" do
|
381
|
-
gateway_timeout =
|
389
|
+
gateway_timeout = RightScale::HttpExceptions.create(504, "server timeout")
|
382
390
|
@no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout})
|
383
391
|
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
384
392
|
raise_error(RightScale::BalancedHttpClient::NotResponding, "server timeout")
|
@@ -386,7 +394,7 @@ describe RightScale::BalancedHttpClient do
|
|
386
394
|
end
|
387
395
|
|
388
396
|
it "uses raise NotResponding if status code is 504 and http_body is nil or empty" do
|
389
|
-
gateway_timeout =
|
397
|
+
gateway_timeout = RightScale::HttpExceptions.create(504, "")
|
390
398
|
@no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout})
|
391
399
|
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
392
400
|
raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
|
@@ -395,7 +403,7 @@ describe RightScale::BalancedHttpClient do
|
|
395
403
|
|
396
404
|
[502, 503].each do |code|
|
397
405
|
it "uses server name in NotResponding exception if status code is #{code}" do
|
398
|
-
e =
|
406
|
+
e = RightScale::HttpExceptions.create(code)
|
399
407
|
@no_result = RightSupport::Net::NoResult.new("no result", {@url => e})
|
400
408
|
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
|
401
409
|
raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
|
@@ -403,34 +411,63 @@ describe RightScale::BalancedHttpClient do
|
|
403
411
|
end
|
404
412
|
|
405
413
|
it "raises last exception in details if not retryable" do
|
406
|
-
bad_request =
|
414
|
+
bad_request = RightScale::HttpExceptions.create(400, "bad data")
|
407
415
|
@no_result = RightSupport::Net::NoResult.new("no result", {@url => bad_request})
|
408
416
|
lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
|
409
417
|
@yielded.should == bad_request
|
410
418
|
end
|
411
419
|
end
|
412
420
|
|
413
|
-
context :
|
421
|
+
context :log_success do
|
422
|
+
before(:each) do
|
423
|
+
@result = {"out" => 123}
|
424
|
+
@body = JSON.dump({:out => 123})
|
425
|
+
@headers = {:status => "200 OK"}
|
426
|
+
@response = [@result, 200, @body, @headers]
|
427
|
+
@client = RightScale::BalancedHttpClient.new(@urls)
|
428
|
+
end
|
429
|
+
|
430
|
+
it "logs response length using header :content_length if available" do
|
431
|
+
@headers.merge!(:content_length => 99)
|
432
|
+
@log.should_receive(:info).with("Completed <uuid> in 10ms | 200 [http://my.com/foo/bar] | 99 bytes").once
|
433
|
+
@client.send(:log_success, @result, 200, @body, @headers, "http://my.com", @path, "uuid", @now, :info).should be true
|
434
|
+
end
|
435
|
+
|
436
|
+
it "logs response length using response body size of :content_lenght not available" do
|
437
|
+
@log.should_receive(:info).with("Completed <uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
438
|
+
@client.send(:log_success, @result, 200, @body, @headers, "http://my.com", @path, "uuid", @now, :info).should be true
|
439
|
+
end
|
440
|
+
|
441
|
+
it "logs duration based on request start time" do
|
442
|
+
@log.should_receive(:info).with("Completed <uuid> in 20ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
443
|
+
@client.send(:log_success, @result, 200, @body, @headers, "http://my.com", @path, "uuid", @now - 0.01, :info).should be true
|
444
|
+
end
|
445
|
+
|
446
|
+
it "log result if log level option set to :debug" do
|
447
|
+
@log.should_receive(:debug).with("Completed <uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
|
448
|
+
@client.send(:log_success, @result, 200, @body, @headers, "http://my.com", @path, "uuid", @now, :debug).should be true
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
context :log_failure do
|
414
453
|
before(:each) do
|
415
|
-
@started_at = Time.now
|
416
|
-
flexmock(Time).should_receive(:now).and_return(@started_at + 0.01)
|
417
454
|
@client = RightScale::BalancedHttpClient.new(@urls)
|
418
455
|
end
|
419
456
|
|
420
457
|
it "logs exception" do
|
421
|
-
exception =
|
458
|
+
exception = RightScale::HttpExceptions.create(400, "bad data")
|
422
459
|
@log.should_receive(:error).with("Failed <uuid> in 10ms | 400 [http://my.com/foo/bar \"params\"] | 400 Bad Request: bad data").once
|
423
|
-
@client.send(:
|
460
|
+
@client.send(:log_failure, @url, @path, "params", [], "uuid", @now, :info, exception).should be true
|
424
461
|
end
|
425
462
|
|
426
463
|
it "logs error string" do
|
427
464
|
@log.should_receive(:error).with("Failed <uuid> in 10ms | nil [http://my.com/foo/bar \"params\"] | bad data").once
|
428
|
-
@client.send(:
|
465
|
+
@client.send(:log_failure, @url, @path, "params", [], "uuid", @now, :info, "bad data").should be true
|
429
466
|
end
|
430
467
|
|
431
|
-
it "filters
|
468
|
+
it "filters parameters" do
|
432
469
|
@log.should_receive(:error).with("Failed <uuid> in 10ms | nil [http://my.com/foo/bar {:secret=>\"<hidden>\"}] | bad data").once
|
433
|
-
@client.send(:
|
470
|
+
@client.send(:log_failure, @url, @path, {:secret => "data"}, ["secret"], "uuid", @now, :info, "bad data").should be true
|
434
471
|
end
|
435
472
|
end
|
436
473
|
|
@@ -443,22 +480,21 @@ describe RightScale::BalancedHttpClient do
|
|
443
480
|
|
444
481
|
context "in info mode with no host" do
|
445
482
|
it "generates text containing path" do
|
446
|
-
text = @client.send(:log_text, @path, {:value => 123}, [])
|
483
|
+
text = @client.send(:log_text, @path, {:value => 123}, [], :info)
|
447
484
|
text.should == "/foo/bar"
|
448
485
|
end
|
449
486
|
end
|
450
487
|
|
451
488
|
context "in info mode with host" do
|
452
489
|
it "generates text containing host and path" do
|
453
|
-
text = @client.send(:log_text, @path, {:value => 123}, [], @url)
|
490
|
+
text = @client.send(:log_text, @path, {:value => 123}, [], :info, @url)
|
454
491
|
text.should == "[http://my.com/foo/bar]"
|
455
492
|
end
|
456
493
|
end
|
457
494
|
|
458
495
|
context "and in debug mode" do
|
459
|
-
it "generates text containing containing host, path, and filtered
|
460
|
-
@
|
461
|
-
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url)
|
496
|
+
it "generates text containing containing host, path, and filtered parameters" do
|
497
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], :debug, @url)
|
462
498
|
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}]"
|
463
499
|
end
|
464
500
|
end
|
@@ -466,13 +502,13 @@ describe RightScale::BalancedHttpClient do
|
|
466
502
|
|
467
503
|
context "when exception" do
|
468
504
|
it "includes params regardless of mode" do
|
469
|
-
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url, "failed")
|
505
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], :info, @url, "failed")
|
470
506
|
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}] | failed"
|
471
507
|
end
|
472
508
|
|
473
509
|
it "includes exception text" do
|
474
|
-
exception =
|
475
|
-
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url, exception)
|
510
|
+
exception = RightScale::HttpExceptions.create(400, "bad data")
|
511
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], :info, @url, exception)
|
476
512
|
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}] | 400 Bad Request: bad data"
|
477
513
|
end
|
478
514
|
end
|
@@ -489,7 +525,7 @@ describe RightScale::BalancedHttpClient do
|
|
489
525
|
@client.send(:filter, params, filter).should == {:some => 1, "secret" => "<hidden>"}
|
490
526
|
end
|
491
527
|
|
492
|
-
it "converts
|
528
|
+
it "converts parameter names to string before comparing to filter list" do
|
493
529
|
filter = ["secret", "very secret"]
|
494
530
|
params = {:some => 1, :secret => "data", "very secret" => "data"}
|
495
531
|
@client.send(:filter, params, filter).should == {:some => 1, :secret => "<hidden>", "very secret" => "<hidden>"}
|
@@ -527,41 +563,110 @@ describe RightScale::BalancedHttpClient do
|
|
527
563
|
end
|
528
564
|
end
|
529
565
|
|
566
|
+
context :format do
|
567
|
+
before(:each) do
|
568
|
+
@client = RightScale::BalancedHttpClient
|
569
|
+
end
|
570
|
+
|
571
|
+
it "converts param to key=value format" do
|
572
|
+
@client.format({:foo => "bar"}).should == "foo=bar"
|
573
|
+
end
|
574
|
+
|
575
|
+
it "escapes param value" do
|
576
|
+
@client.format({:path => "/foo/bar"}).should == "path=%2Ffoo%2Fbar"
|
577
|
+
end
|
578
|
+
|
579
|
+
it "breaks arrays into multiple params" do
|
580
|
+
params = {:paths => ["/foo/bar", "/foo/bar2"]}
|
581
|
+
@client.format(params).should == "paths[]=%2Ffoo%2Fbar&paths[]=%2Ffoo%2Fbar2"
|
582
|
+
end
|
583
|
+
|
584
|
+
it "separates params with '&'" do
|
585
|
+
params = {:foo => 111, :paths => ["/foo/bar", "/foo/bar2"], :bar => 999}
|
586
|
+
response = @client.format(params)
|
587
|
+
response.split("&").sort.should == ["bar=999", "foo=111", "paths[]=%2Ffoo%2Fbar", "paths[]=%2Ffoo%2Fbar2"]
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
context :process_response do
|
592
|
+
before(:each) do
|
593
|
+
@result = {"out" => 123}
|
594
|
+
@body = JSON.dump({:out => 123})
|
595
|
+
@client = RightScale::BalancedHttpClient
|
596
|
+
end
|
597
|
+
|
598
|
+
it "returns location header for 201 response" do
|
599
|
+
@client.response(201, "", {:status => "201 Created", :location => "/href"}, false).should == "/href"
|
600
|
+
end
|
601
|
+
|
602
|
+
it "returns body without decoding by default" do
|
603
|
+
@client.response(200, @body, {:status => "200 OK"}, false).should == @body
|
604
|
+
end
|
605
|
+
|
606
|
+
it "returns JSON decoded response if decoding requested" do
|
607
|
+
@client.response(200, @body, {:status => "200 OK"}, true).should == @result
|
608
|
+
end
|
609
|
+
|
610
|
+
it "returns nil if JSON decoded response is empty" do
|
611
|
+
@client.response(200, "null", {:status => "200 OK"}, true).should be nil
|
612
|
+
end
|
613
|
+
|
614
|
+
it "returns nil if response is empty" do
|
615
|
+
@client.response(204, nil, {:status => "204 No Content"}, false).should be nil
|
616
|
+
end
|
617
|
+
|
618
|
+
it "returns nil if response is nil" do
|
619
|
+
@client.response(200, nil, {:status => "200 OK"}, false).should be nil
|
620
|
+
end
|
621
|
+
|
622
|
+
it "returns nil if body is empty" do
|
623
|
+
@client.response(200, "", {:status => "200 OK"}, false).should be nil
|
624
|
+
end
|
625
|
+
|
626
|
+
it "raises exception if response code indicates failure" do
|
627
|
+
lambda { @client.response(400, nil, {:status => "400 Bad Request"}, false) }.should raise_error(RightScale::HttpExceptions::BadRequest)
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
530
631
|
context :exception_text do
|
632
|
+
before(:each) do
|
633
|
+
@client = RightScale::BalancedHttpClient
|
634
|
+
end
|
635
|
+
|
531
636
|
context "when string exception" do
|
532
637
|
it "adds exception text" do
|
533
|
-
|
638
|
+
@client.exception_text("failed").should == "failed"
|
534
639
|
end
|
535
640
|
end
|
536
641
|
|
537
642
|
context "when REST exception" do
|
538
643
|
it "adds exception code/type and any http_body" do
|
539
|
-
exception =
|
540
|
-
|
644
|
+
exception = RightScale::HttpExceptions.create(400, "bad data")
|
645
|
+
@client.exception_text(exception).should == "400 Bad Request: bad data"
|
541
646
|
end
|
542
647
|
|
543
648
|
it "adds exception code/type but omits http_body if it is html" do
|
544
|
-
exception =
|
545
|
-
|
649
|
+
exception = RightScale::HttpExceptions.create(400, "<html> bad </html>")
|
650
|
+
@client.exception_text(exception).should == "400 Bad Request"
|
546
651
|
end
|
547
652
|
|
548
653
|
it "adds exception code/type and omits http_body if it is blank" do
|
549
|
-
exception =
|
550
|
-
|
654
|
+
exception = RightScale::HttpExceptions.create(400, "")
|
655
|
+
@client.exception_text(exception).should == "400 Bad Request"
|
551
656
|
end
|
552
657
|
end
|
553
658
|
|
554
659
|
context "when NoResult exception" do
|
555
660
|
it "adds exception class and message" do
|
556
661
|
exception = RightSupport::Net::NoResult.new("no result")
|
557
|
-
|
662
|
+
@client.exception_text(exception).should == "RightSupport::Net::NoResult: no result"
|
558
663
|
end
|
559
664
|
end
|
560
665
|
|
561
666
|
context "when non-REST, non-NoResult exception" do
|
562
667
|
it "adds exception class and message" do
|
563
668
|
exception = ArgumentError.new("bad arg")
|
564
|
-
|
669
|
+
@client.exception_text(exception).should == "ArgumentError: bad arg"
|
565
670
|
end
|
566
671
|
end
|
567
672
|
|
@@ -569,7 +674,7 @@ describe RightScale::BalancedHttpClient do
|
|
569
674
|
it "adds exception class, message, and backtrace" do
|
570
675
|
exception = ArgumentError.new("bad arg")
|
571
676
|
flexmock(exception).should_receive(:backtrace).and_return(["line 1", "line 2"])
|
572
|
-
|
677
|
+
@client.exception_text(exception).should == "ArgumentError: bad arg in\nline 1\nline 2"
|
573
678
|
end
|
574
679
|
end
|
575
680
|
end
|