right_agent 2.0.8-x86-mingw32 → 2.1.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -103,7 +103,7 @@ describe RightScale::BaseRetryClient do
103
103
 
104
104
  it "sets up for reconnect if not connected" do
105
105
  flexmock(RightScale::BalancedHttpClient).should_receive(:new).and_return(@http_client).once
106
- e = RightScale::BalancedHttpClient::NotResponding.new(nil, RestExceptionMock.new(503))
106
+ e = RightScale::BalancedHttpClient::NotResponding.new(nil, RightScale::HttpExceptions.create(503))
107
107
  @http_client.should_receive(:check_health).and_raise(e)
108
108
  @log.should_receive(:error)
109
109
  flexmock(@client).should_receive(:enable_use).never
@@ -291,9 +291,9 @@ describe RightScale::BaseRetryClient do
291
291
  end
292
292
 
293
293
  it "sets state to :disconnected and logs if server not responding" do
294
- e = RightScale::BalancedHttpClient::NotResponding.new("not responding", RestExceptionMock.new(503))
294
+ e = RightScale::BalancedHttpClient::NotResponding.new("not responding", RightScale::HttpExceptions.create(503))
295
295
  @http_client.should_receive(:check_health).and_raise(e).once
296
- @log.should_receive(:error).with("Failed test health check", RestExceptionMock).once
296
+ @log.should_receive(:error).with("Failed test health check", RightScale::HttpException).once
297
297
  @client.send(:check_health).should == :disconnected
298
298
  @client.state.should == :disconnected
299
299
  end
@@ -490,7 +490,7 @@ describe RightScale::BaseRetryClient do
490
490
  context "when redirect" do
491
491
  [301, 302].each do |http_code|
492
492
  it "handles #{http_code} redirect" do
493
- e = RestExceptionMock.new(http_code, "redirect")
493
+ e = RightScale::HttpExceptions.create(http_code, "redirect")
494
494
  flexmock(@client).should_receive(:handle_redirect).with(e, @type, @request_uuid).once
495
495
  @client.send(:handle_exception, e, @type, @request_uuid, @now, 1)
496
496
  end
@@ -498,27 +498,27 @@ describe RightScale::BaseRetryClient do
498
498
  end
499
499
 
500
500
  it "raises if unauthorized" do
501
- e = RestExceptionMock.new(401, "unauthorized")
501
+ e = RightScale::HttpExceptions.create(401, "unauthorized")
502
502
  lambda { @client.send(:handle_exception, e, @type, @request_uuid, @now, 1) }.should \
503
503
  raise_error(RightScale::Exceptions::Unauthorized, "unauthorized")
504
504
  end
505
505
 
506
506
  it "notifies auth client and raises retryable if session expired" do
507
- e = RestExceptionMock.new(403, "forbidden")
507
+ e = RightScale::HttpExceptions.create(403, "forbidden")
508
508
  lambda { @client.send(:handle_exception, e, @type, @request_uuid, @now, 1) }.should \
509
509
  raise_error(RightScale::Exceptions::RetryableError, "Authorization expired")
510
510
  @auth_client.expired_called.should be_true
511
511
  end
512
512
 
513
513
  it "handles retry with and updates request_uuid to distinguish for retry" do
514
- e = RestExceptionMock.new(449, "retry with")
514
+ e = RightScale::HttpExceptions.create(449, "retry with")
515
515
  flexmock(@client).should_receive(:handle_retry_with).with(e, @type, @request_uuid, @now, 1).
516
516
  and_return("modified uuid").once
517
517
  @client.send(:handle_exception, e, @type, @request_uuid, @now, 1).should == "modified uuid"
518
518
  end
519
519
 
520
520
  it "handles internal server error" do
521
- e = RestExceptionMock.new(500, "test internal error")
521
+ e = RightScale::HttpExceptions.create(500, "test internal error")
522
522
  lambda { @client.send(:handle_exception, e, @type, @request_uuid, @now, 1) }.should \
523
523
  raise_error(RightScale::Exceptions::InternalServerError, "test internal error")
524
524
  end
@@ -530,7 +530,7 @@ describe RightScale::BaseRetryClient do
530
530
  end
531
531
 
532
532
  it "causes other HTTP exceptions to be re-raised by returning nil" do
533
- e = RestExceptionMock.new(400, "bad request")
533
+ e = RightScale::HttpExceptions.create(400, "bad request")
534
534
  @client.send(:handle_exception, e, @type, @request_uuid, @now, 1).should be_nil
535
535
  end
536
536
 
@@ -542,7 +542,7 @@ describe RightScale::BaseRetryClient do
542
542
  context :handle_redirect do
543
543
  it "initiates redirect by notifying auth client and raising retryable error" do
544
544
  location = "http://somewhere.com"
545
- e = RestExceptionMock.new(301, "moved permanently", {:location => location})
545
+ e = RightScale::HttpExceptions.create(301, "moved permanently", {:location => location})
546
546
  @log.should_receive(:info).with(/Received REDIRECT/).once.ordered
547
547
  @log.should_receive(:info).with("Requesting auth client to handle redirect to #{location.inspect}").once.ordered
548
548
  lambda { @client.send(:handle_redirect, e, @type, @request_uuid) }.should \
@@ -551,7 +551,7 @@ describe RightScale::BaseRetryClient do
551
551
  end
552
552
 
553
553
  it "raises internal error if no redirect location is provided" do
554
- e = RestExceptionMock.new(301, "moved permanently")
554
+ e = RightScale::HttpExceptions.create(301, "moved permanently")
555
555
  @log.should_receive(:info).with(/Received REDIRECT/).once
556
556
  lambda { @client.send(:handle_redirect, e, @type, @request_uuid) }.should \
557
557
  raise_error(RightScale::Exceptions::InternalServerError, "No redirect location provided")
@@ -560,10 +560,10 @@ describe RightScale::BaseRetryClient do
560
560
 
561
561
  context :handle_retry_with do
562
562
  before(:each) do
563
- @exception = RestExceptionMock.new(449, "retry with")
563
+ @exception = RightScale::HttpExceptions.create(449, "retry with")
564
564
  end
565
565
 
566
- it "sleeps for configured interval and does not raise if retry still viable" do
566
+ it "waits for configured interval and does not raise if retry still viable" do
567
567
  @log.should_receive(:error).with(/Retrying type request/).once
568
568
  flexmock(@client).should_receive(:sleep).with(4).once
569
569
  @client.send(:handle_retry_with, @exception, @type, @request_uuid, @now, 1)
@@ -598,13 +598,13 @@ describe RightScale::BaseRetryClient do
598
598
  @exception = RightScale::BalancedHttpClient::NotResponding.new("Server not responding")
599
599
  end
600
600
 
601
- it "sleeps for configured interval and does not raise if retry still viable" do
601
+ it "waits for configured interval and does not raise if retry still viable" do
602
602
  @log.should_receive(:error).with(/Retrying type request/).once
603
603
  flexmock(@client).should_receive(:sleep).with(4).once
604
604
  @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 1)
605
605
  end
606
606
 
607
- it "changes sleep interval for successive retries" do
607
+ it "changes wait interval for successive retries" do
608
608
  @log.should_receive(:error).with(/Retrying type request/).once
609
609
  flexmock(@client).should_receive(:sleep).with(12).once
610
610
  @client.send(:handle_not_responding, @exception, @type, @request_uuid, @now, 2)
@@ -636,4 +636,20 @@ describe RightScale::BaseRetryClient do
636
636
  end
637
637
  end
638
638
  end
639
+
640
+ context :wait do
641
+ it "waits using timer if non-blocking enabled" do
642
+ @fiber = flexmock("fiber", :resume => true).by_default
643
+ flexmock(Fiber).should_receive(:current).and_return(@fiber)
644
+ flexmock(Fiber).should_receive(:yield).once
645
+ flexmock(EM).should_receive(:add_timer).with(1, Proc).and_yield.once
646
+ @client.init(:test, @auth_client, @options.merge(:non_blocking => true))
647
+ @client.send(:wait, 1).should be true
648
+ end
649
+
650
+ it " waits using sleep if non-blocking disabled" do
651
+ flexmock(@client).should_receive(:sleep).with(1).once
652
+ @client.send(:wait, 1).should be true
653
+ end
654
+ end
639
655
  end
@@ -0,0 +1,265 @@
1
+ #--
2
+ # Copyright (c) 2013-2014 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'blocking_client'))
26
+
27
+ describe RightScale::BlockingClient do
28
+
29
+ include FlexMock::ArgumentTypes
30
+
31
+ before(:each) do
32
+ @log = flexmock(RightScale::Log)
33
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
34
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
35
+ @url = "http://my.com"
36
+ @urls = [@url]
37
+ @host = @url
38
+ @path = "/foo/bar"
39
+ @balancer = flexmock("balancer")
40
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer).by_default
41
+ @later = (@now = Time.now)
42
+ flexmock(Time).should_receive(:now).and_return { @later += 1 }
43
+ end
44
+
45
+ context :initialize do
46
+ before(:each) do
47
+ @options = {}
48
+ @client = RightScale::BlockingClient.new(@options)
49
+ end
50
+
51
+ it "does not initialize use of proxy if not defined in environment" do
52
+ flexmock(RestClient).should_receive(:proxy=).never
53
+ RightScale::BlockingClient.new(@options)
54
+ end
55
+
56
+ ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY'].each do |proxy|
57
+ context "when #{proxy} defined in environment" do
58
+ after(:each) do
59
+ ENV.delete(proxy)
60
+ end
61
+
62
+ it "initializes use of proxy" do
63
+ ENV[proxy] = "https://my.proxy.com"
64
+ flexmock(RestClient).should_receive(:proxy=).with("https://my.proxy.com").once
65
+ RightScale::BlockingClient.new(@options)
66
+ end
67
+
68
+ it "infers http scheme for proxy address" do
69
+ ENV[proxy] = "1.2.3.4"
70
+ flexmock(RestClient).should_receive(:proxy=).with("http://1.2.3.4").once
71
+ RightScale::BlockingClient.new(@options)
72
+ end
73
+ end
74
+ end
75
+
76
+ it "constructs health check proc" do
77
+ @client.health_check_proc.should be_a Proc
78
+ end
79
+
80
+ context "health check proc" do
81
+ before(:each) do
82
+ @http_client = flexmock("http client")
83
+ flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
84
+ end
85
+
86
+ it "removes user and password from URL when checking health" do
87
+ @url = "http://111:secret@me.com"
88
+ @client = RightScale::BlockingClient.new(@options.merge(:health_check_path => "/health-check-me"))
89
+ flexmock(@client).should_receive(:request).with(:get, "", "http://me.com/health-check-me", {}, Hash).once
90
+ @client.health_check_proc.call(@url)
91
+ end
92
+
93
+ it "uses default path if none specified" do
94
+ @client = RightScale::BlockingClient.new(@options)
95
+ flexmock(@client).should_receive(:request).with(:get, "", "http://my.com/health-check", {}, Hash).once
96
+ @client.health_check_proc.call(@url)
97
+ end
98
+
99
+ it "appends health check path to any existing path" do
100
+ @url = "http://my.com/foo"
101
+ @client = RightScale::BlockingClient.new(@options)
102
+ flexmock(@client).should_receive(:request).with(:get, "", "http://my.com/foo/health-check", {}, Hash).once
103
+ @client.health_check_proc.call(@url)
104
+ end
105
+
106
+ it "uses fixed timeout values" do
107
+ @client = RightScale::BlockingClient.new(:open_timeout => 5, :request_timeout => 30)
108
+ flexmock(@client).should_receive(:request).with(:get, "", String, {}, {:open_timeout => 2, :timeout => 5}).once
109
+ @client.health_check_proc.call(@url)
110
+ end
111
+
112
+ it "sets API version if specified" do
113
+ @client = RightScale::BlockingClient.new(:api_version => "2.0")
114
+ flexmock(@client).should_receive(:request).with(:get, "", String, {}, hsh(:headers => {"X-API-Version" => "2.0"})).once
115
+ @client.health_check_proc.call(@url)
116
+ end
117
+ end
118
+ end
119
+
120
+ context :options do
121
+ before(:each) do
122
+ @options = {}
123
+ @verb = :get
124
+ @params = {}
125
+ @headers = {}
126
+ @client = RightScale::BlockingClient.new(@options)
127
+ end
128
+
129
+ it "sets default open and request timeouts" do
130
+ connect_options, request_options = @client.options(@verb, @path, @params, @headers, @options)
131
+ connect_options.should == {}
132
+ request_options[:open_timeout].should == RightScale::BalancedHttpClient::DEFAULT_OPEN_TIMEOUT
133
+ request_options[:timeout].should == RightScale::BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT
134
+ end
135
+
136
+ it "sets specified open and request timeouts" do
137
+ @options = {:open_timeout => 1, :request_timeout => 2}
138
+ _, request_options = @client.options(@verb, @path, @params, @headers, @options)
139
+ request_options[:open_timeout].should == 1
140
+ request_options[:timeout].should == 2
141
+ end
142
+
143
+ it "sets request headers" do
144
+ @headers = {:some => "header"}
145
+ _, request_options = @client.options(@verb, @path, @params, @headers, @options)
146
+ request_options[:headers].should == @headers
147
+ end
148
+
149
+ [:get, :delete].each do |verb|
150
+ context "with #{verb.inspect}" do
151
+ it "uses form-encoded query option for parameters" do
152
+ @params = {:some => "data", :more => ["a", "b"]}
153
+ _, request_options = @client.options(verb, @path, @params, @headers, @options)
154
+ request_options[:query].should == "?some=data&more[]=a&more[]=b"
155
+ request_options[:payload].should be nil
156
+ end
157
+
158
+ it "omits query option if there are no parameters" do
159
+ _, request_options = @client.options(verb, @path, @params, @headers, @options)
160
+ request_options.has_key?(:query).should be false
161
+ end
162
+ end
163
+ end
164
+
165
+ [:post, :put].each do |verb|
166
+ context "with #{verb.inspect}" do
167
+ it "uses JSON-encoded payload options for parameters" do
168
+ @params = {:some => "data"}
169
+ _, request_options = @client.options(verb, @path, @params, @headers, @options)
170
+ request_options[:query].should be_nil
171
+ request_options[:payload].should == JSON.dump(@params)
172
+ request_options[:headers][:content_type].should == "application/json"
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ context "requesting" do
179
+ before(:each) do
180
+ @options = {}
181
+ @query = "?some=data&more[]=a&more[]=b"
182
+ @connect_options = {}
183
+ @request_options = {:open_timeout => 2, :request_timeout => 5, :headers => {:accept => "application/json"}, :query => @query}
184
+ @result = {"out" => 123}
185
+ @body = JSON.dump({:out => 123})
186
+ @headers = {:status => "200 OK"}
187
+ @response = flexmock("response", :code => 200, :body => @body, :headers => @headers).by_default
188
+ @http_client = flexmock("http client")
189
+ flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
190
+ @client = RightScale::BlockingClient.new(@options)
191
+ end
192
+
193
+ context :request do
194
+ it "makes request" do
195
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@response).once
196
+ @client.request(:get, @path, @host, @connect_options, @request_options)
197
+ @request_options[:query].should be nil
198
+ end
199
+
200
+ it "processes response and returns result plus response code, body, and headers" do
201
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@response).once
202
+ result = @client.request(:get, @path, @host, @connect_options, @request_options)
203
+ result.should == [@result, 200, @body, @headers]
204
+ end
205
+
206
+ it "returns nil if response is nil" do
207
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(nil).once
208
+ result = @client.request(:get, @path, @host, @connect_options, @request_options)
209
+ result.should == [nil, nil, nil, nil]
210
+ end
211
+ end
212
+
213
+ context :poll do
214
+ before(:each) do
215
+ @stop_at = @later + 10
216
+ @connection = {:host => @host, :path => @path, :expires_at => @later}
217
+ @nil_response = flexmock("nil response", :code => 200, :body => "null", :headers => @headers).by_default
218
+ end
219
+
220
+ it "uses existing connection info to form url" do
221
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@response).once
222
+ @client.poll(@connection, @request_options, @stop_at)
223
+ end
224
+
225
+ it "makes request" do
226
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@response).once
227
+ result = @client.poll(@connection, @request_options, @stop_at)
228
+ result.should == [@result, 200, @body, @headers]
229
+ end
230
+
231
+ it "makes at least one request regardless of stop time" do
232
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@nil_response).once
233
+ result = @client.poll(@connection, @request_options, @now - 10)
234
+ result.should == [nil, 200, "null", @headers]
235
+ end
236
+
237
+ it "stops polling if there is a non-nil result" do
238
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@nil_response).once.ordered
239
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@response).once.ordered
240
+ result = @client.poll(@connection, @request_options, @stop_at)
241
+ result.should == [@result, 200, @body, @headers]
242
+ end
243
+
244
+ it "stops polling if there is a nil result but it is time to stop" do
245
+ @http_client.should_receive(:get).with(@host + @path + @query, @request_options).and_return(@nil_response).twice
246
+ result = @client.poll(@connection, @request_options, @later + 2)
247
+ result.should == [nil, 200, "null", @headers]
248
+ end
249
+ end
250
+
251
+ context :request_once do
252
+ it "makes request and returns results" do
253
+ @http_client.should_receive(:get).with(@url, @request_options).and_return(@response).once
254
+ result = @client.send(:request_once, :get, @url, @request_options)
255
+ result.should == [@result, 200, @body, @headers]
256
+ end
257
+
258
+ it "returns nil if response is nil" do
259
+ @http_client.should_receive(:get).with(@url, @request_options).and_return(nil).once
260
+ result = @client.send(:request_once, :get, @url, @request_options)
261
+ result.should == [nil, nil, nil, nil]
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,387 @@
1
+ #--
2
+ # Copyright (c) 2013-2014 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'non_blocking_client'))
26
+
27
+ describe RightScale::NonBlockingClient do
28
+
29
+ include FlexMock::ArgumentTypes
30
+
31
+ before(:each) do
32
+ @log = flexmock(RightScale::Log)
33
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
34
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
35
+ @url = "http://my.com"
36
+ @urls = [@url]
37
+ @host = @url
38
+ @path = "/foo/bar"
39
+ @balancer = flexmock("balancer")
40
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer).by_default
41
+ @later = (@now = Time.now)
42
+ flexmock(Time).should_receive(:now).and_return { @later += 1 }
43
+ end
44
+
45
+ context :initialize do
46
+ before(:each) do
47
+ @options = {}
48
+ @client = RightScale::NonBlockingClient.new(@options)
49
+ end
50
+
51
+ it "does not initialize use of proxy if not defined in environment" do
52
+ @client.instance_variable_get(:@proxy).should be nil
53
+ end
54
+
55
+ ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY'].each do |proxy|
56
+ context "when #{proxy} defined in environment" do
57
+ after(:each) do
58
+ ENV.delete(proxy)
59
+ end
60
+
61
+ it "initializes use of proxy" do
62
+ ENV[proxy] = "https://my.proxy.com"
63
+ @client = RightScale::NonBlockingClient.new(@options)
64
+ @client.instance_variable_get(:@proxy).should == {:host => "my.proxy.com", :port => 443}
65
+ end
66
+
67
+ it "infers http scheme for proxy address" do
68
+ ENV[proxy] = "1.2.3.4"
69
+ @client = RightScale::NonBlockingClient.new(@options)
70
+ @client.instance_variable_get(:@proxy).should == {:host => "1.2.3.4", :port => 80}
71
+ end
72
+
73
+ it "applies user and password to proxy address if defined in proxy address" do
74
+ ENV[proxy] = "https://111:secret@my.proxy.com"
75
+ @client = RightScale::NonBlockingClient.new(@options)
76
+ @client.instance_variable_get(:@proxy).should == {:host => "my.proxy.com", :port => 443, :authorization => ["111", "secret"]}
77
+ end
78
+ end
79
+ end
80
+
81
+ it "constructs health check proc" do
82
+ @client.health_check_proc.should be_a Proc
83
+ end
84
+
85
+ context "health check proc" do
86
+ it "removes user and password from URL when checking health" do
87
+ @url = "http://111:secret@me.com"
88
+ @client = RightScale::NonBlockingClient.new(@options.merge(:health_check_path => "/health-check-me"))
89
+ flexmock(@client).should_receive(:request).with(:get, "", "http://me.com", Hash, {:path => "/health-check-me"}).once
90
+ @client.health_check_proc.call(@url)
91
+ end
92
+
93
+ it "uses default path if none specified" do
94
+ flexmock(@client).should_receive(:request).with(:get, "", @host, Hash, {:path => "/health-check"}).once
95
+ @client.health_check_proc.call(@url)
96
+ end
97
+
98
+ it "appends health check path to any existing path" do
99
+ @url = "http://my.com/foo"
100
+ flexmock(@client).should_receive(:request).with(:get, "", @host, Hash, {:path => "/foo/health-check"}).once
101
+ @client.health_check_proc.call(@url)
102
+ end
103
+
104
+ it "uses fixed timeout values" do
105
+ @client = RightScale::NonBlockingClient.new(@options.merge(:open_timeout => 5, :request_timeout => 30))
106
+ connect_options = {:connect_timeout => 2, :inactivity_timeout => 5}
107
+ flexmock(@client).should_receive(:request).with(:get, "", @host, connect_options, Hash).once
108
+ @client.health_check_proc.call(@url)
109
+ end
110
+
111
+ it "sets API version if specified" do
112
+ @client = RightScale::NonBlockingClient.new(@options.merge(:api_version => "2.0"))
113
+ request_options = {:path => "/health-check", :head => {"X-API-Version" => "2.0"}}
114
+ flexmock(@client).should_receive(:request).with(:get, "", @host, Hash, request_options).once
115
+ @client.health_check_proc.call(@url)
116
+ end
117
+
118
+ it "uses proxy if defined" do
119
+ ENV["HTTPS_PROXY"] = "https://my.proxy.com"
120
+ @client = RightScale::NonBlockingClient.new(@options)
121
+ connect_options = {:connect_timeout => 2, :inactivity_timeout => 5, :proxy => {:host => "my.proxy.com", :port => 443}}
122
+ flexmock(@client).should_receive(:request).with(:get, "", @host, connect_options, Hash).once
123
+ @client.health_check_proc.call(@url)
124
+ ENV.delete("HTTPS_PROXY")
125
+ end
126
+ end
127
+ end
128
+
129
+ context :options do
130
+ before(:each) do
131
+ @options = {}
132
+ @verb = :get
133
+ @params = {}
134
+ @headers = {}
135
+ @client = RightScale::NonBlockingClient.new(@options)
136
+ end
137
+
138
+ it "sets default open and request timeouts" do
139
+ connect_options, _ = @client.options(@verb, @path, @params, @headers, @options)
140
+ connect_options[:connect_timeout].should == RightScale::BalancedHttpClient::DEFAULT_OPEN_TIMEOUT
141
+ connect_options[:inactivity_timeout].should == RightScale::BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT
142
+ end
143
+
144
+ it "sets specified open and request timeouts" do
145
+ @options = {:open_timeout => 1, :request_timeout => 2}
146
+ connect_options, _ = @client.options(@verb, @path, @params, @headers, @options)
147
+ connect_options[:connect_timeout].should == 1
148
+ connect_options[:inactivity_timeout].should == 2
149
+ end
150
+
151
+ it "uses poll timeout instead of request timeout if defined" do
152
+ @options = {:open_timeout => 1, :poll_timeout=> 2, :request_timeout => 3}
153
+ connect_options, _ = @client.options(:poll, @path, @params, @headers, @options)
154
+ connect_options[:connect_timeout].should == 1
155
+ connect_options[:inactivity_timeout].should == 2
156
+ end
157
+
158
+ it "does not uses poll timeout if defined but not polling" do
159
+ @options = {:open_timeout => 1, :poll_timeout=> 2, :request_timeout => 3}
160
+ connect_options, _ = @client.options(@verb, @path, @params, @headers, @options)
161
+ connect_options[:connect_timeout].should == 1
162
+ connect_options[:inactivity_timeout].should == 3
163
+ end
164
+
165
+ it "sets :keepalive if this is a poll request" do
166
+ _, request_options = @client.options(:poll, @path, @params, @headers, @options)
167
+ request_options[:keepalive].should be true
168
+ end
169
+
170
+ it "sets request headers" do
171
+ @headers = {:some => "header"}
172
+ _, request_options = @client.options(@verb, @path, @params, @headers, @options)
173
+ request_options[:head].should == @headers
174
+ end
175
+
176
+ it "sets :proxy if defined" do
177
+ ENV["HTTPS_PROXY"] = "https://my.proxy.com"
178
+ @client = RightScale::NonBlockingClient.new(@options)
179
+ connect_options, _ = @client.options(@verb, @path, @params, @headers, @options)
180
+ connect_options[:proxy].should == {:host => "my.proxy.com", :port => 443}
181
+ ENV.delete("HTTPS_PROXY")
182
+ end
183
+
184
+ [:get, :delete].each do |verb|
185
+ context "with #{verb.inspect}" do
186
+ it "appends form-encoded query option to path" do
187
+ @params = {:some => "data"}
188
+ _, request_options = @client.options(verb, @path, @params, @headers, @options)
189
+ request_options[:path].should == "/foo/bar?some=data"
190
+ request_options[:body].should be nil
191
+ end
192
+ end
193
+ end
194
+
195
+ [:post, :put].each do |verb|
196
+ context "with #{verb.inspect}" do
197
+ it "stores JSON-encoded parameters in body" do
198
+ @params = {:some => "data"}
199
+ _, request_options = @client.options(verb, @path, @params, @headers, @options)
200
+ request_options[:path].should == @path
201
+ request_options[:body].should == JSON.dump(@params)
202
+ request_options[:head][:content_type].should == "application/json"
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ context "requesting" do
209
+ before(:each) do
210
+ @options = {}
211
+ @connect_options = {:connect_timeout => 2, :inactivity_timeout => 5}
212
+ @request_options = {:path => @path, :head => {:accept => "application/json"}}
213
+ @result = {"out" => 123}
214
+ @body = JSON.dump({:out => 123})
215
+ @headers = EventMachine::HttpResponseHeader.new
216
+ @headers.http_status = 200
217
+ @headers["STATUS"] = "200 OK"
218
+ @beautified_headers = {:status => "200 OK"}
219
+ @response = flexmock("em http response", :response_header => @headers, :response => @body,
220
+ :error => nil, :errback => true, :callback => true).by_default
221
+ @request = flexmock("em http request", :get => @response).by_default
222
+ flexmock(EM::HttpRequest).should_receive(:new).and_return(@request).by_default
223
+ @fiber = flexmock("fiber", :resume => true).by_default
224
+ flexmock(Fiber).should_receive(:current).and_return(@fiber)
225
+ flexmock(Fiber).should_receive(:yield).and_return([200, @body, @headers]).by_default
226
+ @client = RightScale::NonBlockingClient.new(@options)
227
+ end
228
+
229
+ context :request do
230
+ it "makes request" do
231
+ @response.should_receive(:callback).and_yield.once
232
+ @fiber.should_receive(:resume).with(200, @body, @headers).once
233
+ flexmock(EM::HttpRequest).should_receive(:new).with(@host, @connect_options).and_return(@request).once
234
+ @request.should_receive(:get).with(@request_options).and_return(@response).once
235
+ @client.request(:get, @path, @host, @connect_options, @request_options)
236
+ @client.connections[@path].should be nil
237
+ end
238
+
239
+ it "processes response and returns result plus response code, body, and headers" do
240
+ @response.should_receive(:callback).and_yield.once
241
+ @fiber.should_receive(:resume).with(200, @body, @headers).once
242
+ flexmock(EM::HttpRequest).should_receive(:new).with(@host, @connect_options).and_return(@request).once
243
+ result = @client.request(:get, @path, @host, @connect_options, @request_options)
244
+ result.should == [@result, 200, @body, @beautified_headers]
245
+ end
246
+
247
+ it "handles host value that has path" do
248
+ @response.should_receive(:callback).and_yield.once
249
+ @fiber.should_receive(:resume).with(200, @body, @headers).once
250
+ flexmock(EM::HttpRequest).should_receive(:new).with(@host, @connect_options).and_return(@request).once
251
+ @request.should_receive(:get).with(on { |a| a[:path] == "/api" + @path }).and_return(@response).once
252
+ @client.request(:get, @path, @host + "/api", @connect_options, @request_options)
253
+ @request_options[:path].should == "/api/foo/bar"
254
+ end
255
+
256
+ it "converts Errno::ETIMEDOUT error to 504" do
257
+ @headers.http_status = 504
258
+ @response.should_receive(:errback).and_yield.once
259
+ @response.should_receive(:error).and_return("Errno::ETIMEDOUT")
260
+ @fiber.should_receive(:resume).with(504, "Errno::ETIMEDOUT").once
261
+ flexmock(Fiber).should_receive(:yield).and_return([504, "Errno::ETIMEDOUT"]).once
262
+ flexmock(EM::HttpRequest).should_receive(:new).with(@host, @connect_options).and_return(@request).once
263
+ lambda { @client.request(:get, @path, @host, @connect_options, @request_options) }.
264
+ should raise_error(RightScale::HttpExceptions::GatewayTimeout)
265
+ end
266
+
267
+ it "stores the connection if keepalive enabled" do
268
+ @request_options.merge!(:keepalive => true)
269
+ flexmock(EM::HttpRequest).should_receive(:new).and_return(@request).once
270
+ @client.request(:get, @path, @host, @connect_options, @request_options)
271
+ @client.connections[@path].should == {:host => @host, :connection => @request, :expires_at => @later + 5}
272
+ end
273
+ end
274
+
275
+ context "polling" do
276
+ before(:each) do
277
+ @stop_at = @later + 10
278
+ @request_options.merge!(:keepalive => true)
279
+ @connection = {:host => @host, :connection => @request, :expires_at => @later}
280
+ end
281
+
282
+ context :poll do
283
+ before(:each) do
284
+ flexmock(Fiber).should_receive(:yield).and_return([200, @body, @headers]).once
285
+ end
286
+
287
+ it "polls using specified connection and returns result data" do
288
+ @request.should_receive(:get).with(@request_options).and_return(@response).once
289
+ result = @client.poll(@connection, @request_options, @stop_at)
290
+ result.should be_a Array
291
+ result[1].should == 200
292
+ end
293
+
294
+ it "handles host value that has path" do
295
+ @connection[:host] += "/api"
296
+ @request.should_receive(:get).with(on { |a| a[:path] == "/api" + @path }).and_return(@response).once
297
+ @client.poll(@connection, @request_options, @stop_at)
298
+ end
299
+
300
+ it "beautifies response headers" do
301
+ _, _, _, headers = @client.poll(@connection, @request_options, @stop_at)
302
+ headers.should == @beautified_headers
303
+ end
304
+
305
+ it "processes the response to get the result" do
306
+ result, _, _, _ = @client.poll(@connection, @request_options, @stop_at)
307
+ result.should == @result
308
+ end
309
+
310
+ it "updates the timeout in the connection" do
311
+ @client.poll(@connection, @request_options, @stop_at)
312
+ @connection[:expires_at].should == @later + 5
313
+ end
314
+ end
315
+
316
+ context :poll_again do
317
+ it "makes HTTP get request using specified connection" do
318
+ @client.send(:poll_again, @fiber, @request, @request_options, @stop_at).should be true
319
+ end
320
+
321
+ it "stops polling if there is a non-nil result" do
322
+ @response.should_receive(:callback).and_yield.once
323
+ @fiber.should_receive(:resume).with(200, @body, @headers).once
324
+ @client.send(:poll_again, @fiber, @request, @request_options, @stop_at).should be true
325
+ end
326
+
327
+ it "stops polling if the status code is not 200" do
328
+ @headers.http_status = 204
329
+ @response.should_receive(:callback).and_yield.once
330
+ @response.should_receive(:response).and_return(nil).once
331
+ @fiber.should_receive(:resume).with(204, nil, @headers).once
332
+ @client.send(:poll_again, @fiber, @request, @request_options, @stop_at).should be true
333
+ end
334
+
335
+ it "stops polling if it is time to" do
336
+ @response.should_receive(:callback).and_yield.once
337
+ @response.should_receive(:response).and_return(nil).once
338
+ @fiber.should_receive(:resume).with(200, nil, @headers).once
339
+ @client.send(:poll_again, @fiber, @request, @request_options, @later + 1).should be true
340
+ end
341
+
342
+ it "stops polling if there is an error" do
343
+ @response.should_receive(:error).and_return("some error").twice
344
+ @response.should_receive(:errback).and_yield.once
345
+ @fiber.should_receive(:resume).with(500, "some error").once
346
+ @client.send(:poll_again, @fiber, @request, @request_options, @stop_at).should be true
347
+ end
348
+
349
+ it "otherwise polls again" do
350
+ @response.should_receive(:callback).and_yield.twice
351
+ @response.should_receive(:response).and_return(nil).once.ordered
352
+ @response.should_receive(:response).and_return(@body).once.ordered
353
+ @fiber.should_receive(:resume).once
354
+ @client.send(:poll_again, @fiber, @request, @request_options, @later + 2).should be true
355
+ end
356
+
357
+ it "polls again for JSON-encoded nil result" do
358
+ @response.should_receive(:callback).and_yield.twice
359
+ @response.should_receive(:response).and_return("null").once.ordered
360
+ @response.should_receive(:response).and_return(@body).once.ordered
361
+ @fiber.should_receive(:resume).once
362
+ @client.send(:poll_again, @fiber, @request, @request_options, @later + 2).should be true
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ context :beautify_headers do
369
+ before(:each) do
370
+ @client = RightScale::NonBlockingClient.new(@options)
371
+ end
372
+
373
+ it "beautifies header keys" do
374
+ headers = {"SERVER" => "nginx/1.4.2", "DATE" => "Tue, 25 Mar 2014 22:19:52 GMT",
375
+ "CONTENT_TYPE" => "application/json; charset=utf-8", "CONTENT_LENGTH" => "4", "CONNECTION" => "close",
376
+ "STATUS" => "200 OK", "CACHE_CONTROL" => "private, max-age=0, must-revalidate", "X_RUNTIME" => "1", "SET_COOKIE" => ""}
377
+ result = @client.send(:beautify_headers, headers)
378
+ result.should == {:server => "nginx/1.4.2", :date => "Tue, 25 Mar 2014 22:19:52 GMT",
379
+ :content_type => "application/json; charset=utf-8", :content_length => "4", :connection => "close",
380
+ :status => "200 OK", :cache_control => "private, max-age=0, must-revalidate", :x_runtime => "1", :set_cookie => ""}
381
+ end
382
+
383
+ it "converts hyphenations to underscores" do
384
+ @client.send(:beautify_headers, {"NON-BLOCKING" => "yes"}).should == {:non_blocking => "yes"}
385
+ end
386
+ end
387
+ end