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