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