right_cloud_api_base 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY +2 -0
  3. data/LICENSE +19 -0
  4. data/README.md +14 -0
  5. data/Rakefile +37 -0
  6. data/lib/base/api_manager.rb +707 -0
  7. data/lib/base/helpers/cloud_api_logger.rb +214 -0
  8. data/lib/base/helpers/http_headers.rb +239 -0
  9. data/lib/base/helpers/http_parent.rb +103 -0
  10. data/lib/base/helpers/http_request.rb +173 -0
  11. data/lib/base/helpers/http_response.rb +122 -0
  12. data/lib/base/helpers/net_http_patch.rb +31 -0
  13. data/lib/base/helpers/query_api_patterns.rb +862 -0
  14. data/lib/base/helpers/support.rb +270 -0
  15. data/lib/base/helpers/support.xml.rb +306 -0
  16. data/lib/base/helpers/utils.rb +380 -0
  17. data/lib/base/manager.rb +122 -0
  18. data/lib/base/parsers/json.rb +38 -0
  19. data/lib/base/parsers/plain.rb +36 -0
  20. data/lib/base/parsers/rexml.rb +83 -0
  21. data/lib/base/parsers/sax.rb +200 -0
  22. data/lib/base/routines/cache_validator.rb +184 -0
  23. data/lib/base/routines/connection_proxies/net_http_persistent_proxy.rb +194 -0
  24. data/lib/base/routines/connection_proxies/right_http_connection_proxy.rb +224 -0
  25. data/lib/base/routines/connection_proxy.rb +66 -0
  26. data/lib/base/routines/request_analyzer.rb +122 -0
  27. data/lib/base/routines/request_generator.rb +48 -0
  28. data/lib/base/routines/request_initializer.rb +52 -0
  29. data/lib/base/routines/response_analyzer.rb +152 -0
  30. data/lib/base/routines/response_parser.rb +79 -0
  31. data/lib/base/routines/result_wrapper.rb +75 -0
  32. data/lib/base/routines/retry_manager.rb +106 -0
  33. data/lib/base/routines/routine.rb +98 -0
  34. data/lib/right_cloud_api_base.rb +72 -0
  35. data/lib/right_cloud_api_base_version.rb +37 -0
  36. data/right_cloud_api_base.gemspec +63 -0
  37. data/spec/helpers/query_api_pattern_spec.rb +312 -0
  38. data/spec/helpers/support_spec.rb +211 -0
  39. data/spec/helpers/support_xml_spec.rb +207 -0
  40. data/spec/helpers/utils_spec.rb +179 -0
  41. data/spec/routines/connection_proxies/test_net_http_persistent_proxy_spec.rb +143 -0
  42. data/spec/routines/test_cache_validator_spec.rb +152 -0
  43. data/spec/routines/test_connection_proxy_spec.rb +44 -0
  44. data/spec/routines/test_request_analyzer_spec.rb +106 -0
  45. data/spec/routines/test_response_analyzer_spec.rb +132 -0
  46. data/spec/routines/test_response_parser_spec.rb +228 -0
  47. data/spec/routines/test_result_wrapper_spec.rb +63 -0
  48. data/spec/routines/test_retry_manager_spec.rb +84 -0
  49. data/spec/spec_helper.rb +15 -0
  50. metadata +215 -0
@@ -0,0 +1,44 @@
1
+ #--
2
+ # Copyright (c) 2013 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
25
+
26
+ describe "RightScale::CloudApi::ConnectionProxy" do
27
+ before(:each) do
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::INFO
30
+ @connectionproxy = RightScale::CloudApi::ConnectionProxy.new
31
+ @test_data = {}
32
+ @test_data[:options] = {:cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({:logger => logger})}
33
+ @test_data[:callbacks] = {}
34
+ @test_data[:response] = {}
35
+ allow(@connectionproxy).to receive(:log)
36
+ end
37
+
38
+ it "creates a close connection callback" do
39
+ proxy = double(:request => nil)
40
+ expect(RightScale::CloudApi::ConnectionProxy::NetHttpPersistentProxy).to receive(:new).and_return(proxy)
41
+ @connectionproxy.execute(@test_data)
42
+ expect(@test_data[:callbacks][:close_current_connection]).to be_a(Proc)
43
+ end
44
+ end
@@ -0,0 +1,106 @@
1
+ #--
2
+ # Copyright (c) 2013 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
25
+
26
+ describe "RightScale::CloudApi::RequestAnalyzer" do
27
+ before(:each) do
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::INFO
30
+ @api_manager = RightScale::CloudApi::ApiManager.new({'x' => 'y'},'endpoint', {:logger => logger})
31
+ @api_manager.class.set_routine RightScale::CloudApi::RequestAnalyzer
32
+ @api_manager.class.options[:error_patterns] = []
33
+ end
34
+
35
+ context "request keys" do
36
+ before(:each) do
37
+ @valid_request_keys = RightScale::CloudApi::RequestAnalyzer::REQUEST_KEYS.inject({}) { |result, k| result.merge(k => k.to_s)}
38
+ @valid_request_actions = RightScale::CloudApi::RequestAnalyzer::REQUEST_ACTIONS
39
+ end
40
+ it "creates a pattern for expected keys" do
41
+ expected_result = []
42
+ @valid_request_actions.each do |action|
43
+ expected_result << @valid_request_keys.merge(:action => action)
44
+ expect(@api_manager.class.error_pattern(action, @valid_request_keys)).to eq expected_result
45
+ end
46
+ end
47
+ end
48
+
49
+ context "response keys" do
50
+ before(:each) do
51
+ @valid_response_keys = RightScale::CloudApi::RequestAnalyzer::RESPONSE_KEYS.inject({}) { |result, k| result.merge(k => k.to_s)}
52
+ @valid_response_actions = RightScale::CloudApi::RequestAnalyzer::RESPONSE_ACTIONS
53
+ end
54
+ it "creates a pattern for expected keys" do
55
+ expected_result = []
56
+ @valid_response_actions.each do |action|
57
+ expected_result << @valid_response_keys.merge(:action => action)
58
+ expect(@api_manager.class.error_pattern(action, @valid_response_keys)).to eq expected_result
59
+ end
60
+ end
61
+ end
62
+
63
+ context "with unexpected keys" do
64
+ it "fails to create a pattern" do
65
+ valid_request_action = :abort_on_timeout
66
+ expect { @api_manager.class.error_pattern(valid_request_action, {:bad_key => "BadKey"}) }.to raise_error(RightScale::CloudApi::RequestAnalyzer::Error)
67
+ expect { @api_manager.class.error_pattern(:bad_action, {:verb => /verb/}) }.to raise_error(RightScale::CloudApi::RequestAnalyzer::Error)
68
+ expect { @api_manager.class.error_pattern(valid_request_action, {:response => /verb/}) }.to raise_error(RightScale::CloudApi::RequestAnalyzer::Error)
69
+ end
70
+ end
71
+
72
+ context "process" do
73
+ before(:each) do
74
+ logger = Logger.new(STDOUT)
75
+ logger.level = Logger::INFO
76
+ error_patterns = [{:path =>/Action=(Run|Create)/,
77
+ :action =>:abort_on_timeout},
78
+ {:response => /InternalError|Internal Server Error|internal service error/i,
79
+ :action => :retry}]
80
+ @data = {:request => {:instance => 'some_instance',
81
+ :verb => 'some_verb',
82
+ :orig_params => {}},
83
+ :options => {:error_patterns => error_patterns,
84
+ :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({:logger => logger,
85
+ :log_filters => [:request_generator] })}}
86
+ @requestanalyzer = RightScale::CloudApi::RequestAnalyzer.new
87
+ allow(@requestanalyzer).to receive(:log)
88
+ end
89
+
90
+ context "when patern does not match" do
91
+ it "does not set :abort_on_timeout flag" do
92
+ expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).and_return(false)
93
+ @requestanalyzer.execute(@data)
94
+ expect(@data[:options].has_key?(:abort_on_timeout)).to be(false)
95
+ end
96
+ end
97
+
98
+ context "when patern matches" do
99
+ it "sets :abort_on_timeout flag" do
100
+ expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).and_return(true)
101
+ @requestanalyzer.execute(@data)
102
+ expect(@data[:options][:abort_on_timeout]).to be(true)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,132 @@
1
+ #--
2
+ # Copyright (c) 2013 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
25
+
26
+ describe "RightScale::CloudApi::ResponseAnalyzer" do
27
+ before(:each) do
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::INFO
30
+ @responseanalyzer = RightScale::CloudApi::ResponseAnalyzer.new
31
+ @test_data = {}
32
+ @test_data[:request] = { :verb => 'some_verb', :orig_params => {}, :instance => 'some_request'}
33
+ @test_data[:vars] = { :retry => {} }
34
+ @test_data[:options] = {:error_patterns => [], :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new(
35
+ {:logger => logger, :log_filters => [:response_analyzer, :response_analyzer_body_error]})
36
+ }
37
+ @close_current_connection_callback = double
38
+ @test_data[:callbacks] = {:close_current_connection => @close_current_connection_callback}
39
+ @test_data[:connection] = {}
40
+ allow(@responseanalyzer).to receive(:log)
41
+ end
42
+
43
+ context "with no error patterns" do
44
+ it "fails on 5xx, 4xx errors" do
45
+ [500, 400].each do |http_error|
46
+ @test_data[:response] = {:instance => generate_http_response(http_error)}
47
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
48
+ end
49
+ end
50
+ it "fails on redirect when there is no location tin the response" do
51
+ @test_data[:response] = {:instance => generate_http_response(300)}
52
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
53
+ end
54
+ it "works for 2xx codes" do
55
+ @test_data[:response] = {:instance => generate_http_response(200)}
56
+ expect { @responseanalyzer.execute(@test_data) }.to_not raise_error
57
+ end
58
+ end
59
+
60
+ context "when retry is requested" do
61
+ before(:each) do
62
+ error_patterns = [{:action => :retry}]
63
+ @test_data[:options][:error_patterns] = error_patterns
64
+ allow(RightScale::CloudApi::Utils).to receive(:pattern_matches?).and_return(true)
65
+ end
66
+ it "raises a retry attempt exception for 4xx and 5xx errors" do
67
+ [500, 400].each do |http_error|
68
+ @test_data[:response] = {:instance => generate_http_response(http_error)}
69
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
70
+ end
71
+ end
72
+ it "fails when there is no location for 3xx redirect" do
73
+ response = generate_http_response(301, 'body', {'location' => ''})
74
+ @test_data[:response] = {:instance => response}
75
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
76
+ end
77
+ it "raises a retry attempt exception when there is a location (in headers) for 3xx redirect" do
78
+ response = generate_http_response(301, 'body', {'location' => 'www.some-new-location.com'})
79
+ @test_data[:response] = {:instance => response}
80
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
81
+ end
82
+ it "raises a retry attempt exception when there is a location (in body) for 3xx redirect" do
83
+ response = generate_http_response(301, '<Endpoint> www.some-new-location.com </Endpoint>', {'location' => ''})
84
+ @test_data[:response] = {:instance => response}
85
+ uri_object = double(:host= => 'www.some-new-location.com', :to_s => 'www.some-new-location.com')
86
+ @test_data[:connection][:uri] = uri_object
87
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
88
+ end
89
+ end
90
+
91
+ context "when reconnect_and_retry is requested" do
92
+ before(:each) do
93
+ @test_data[:options][:error_patterns] = [{:action => :reconnect_and_retry}]
94
+ expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).at_least(1).and_return(true)
95
+ expect(@close_current_connection_callback).to receive(:call).twice.with(/Error code/)
96
+ end
97
+ it "raises a retry attempt exception for 4xx and 5xx errors" do
98
+ [500, 400].each do |http_error|
99
+ @test_data[:response] = {:instance => generate_http_response(http_error)}
100
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
101
+ end
102
+ end
103
+ end
104
+
105
+ context "when disconnect_and_abort is requested" do
106
+ before(:each) do
107
+ @test_data[:options][:error_patterns] = [{:action => :disconnect_and_abort}]
108
+ expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).at_least(1).and_return(true)
109
+ expect(@close_current_connection_callback).to receive(:call).twice.with(/Error code/)
110
+ end
111
+ it "failse for 4xx and 5xx errors" do
112
+ [500, 400].each do |http_error|
113
+ @test_data[:response] = {:instance => generate_http_response(http_error)}
114
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
115
+ end
116
+ end
117
+ end
118
+
119
+ context "when abort is requested" do
120
+ before(:each) do
121
+ @test_data[:options][:error_patterns] = [{:action => :abort}]
122
+ expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).at_least(1).and_return(true)
123
+ expect(@close_current_connection_callback).to receive(:call).never
124
+ end
125
+ it "failse for 4xx and 5xx errors" do
126
+ [500, 400].each do |http_error|
127
+ @test_data[:response] = {:instance => generate_http_response(http_error)}
128
+ expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,228 @@
1
+ #--
2
+ # Copyright (c) 2013 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
25
+
26
+
27
+ describe "RightScale::CloudApi::ResponseParser" do
28
+ before :each do
29
+ logger = Logger.new(STDOUT)
30
+ logger.level = Logger::INFO
31
+ @response_parser = RightScale::CloudApi::ResponseParser.new
32
+ @test_data = {}
33
+ @test_data[:request] = { :verb => 'some_verb', :orig_params => {}, :instance => 'some_request'}
34
+ @test_data[:options] = {:error_patterns => [], :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new(
35
+ {:logger => logger, :log_filters => [:response_parser]})
36
+ }
37
+ @callback = double
38
+ @test_data[:callbacks] = {:close_current_connection => @callback}
39
+ @test_data[:connection] = {}
40
+ allow(@response_parser).to receive(:log)
41
+ end
42
+
43
+
44
+ context "encoding" do
45
+ context "text/xml;charset=UTF-8" do
46
+ before :each do
47
+ response = double(
48
+ :code => '200',
49
+ :body => 'body',
50
+ :headers => {"content-type" => ["text/xml;charset=UTF-8"]},
51
+ :is_io? => false,
52
+ :is_error? => true,
53
+ :body_info_for_log => 'body',
54
+ :header_info_for_log => ""
55
+ )
56
+ @test_data[:response] = {:instance => response}
57
+ expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
58
+ once.with('body', {:encoding => 'UTF-8'})
59
+ end
60
+
61
+ it "works" do
62
+ @response_parser.execute(@test_data)
63
+ end
64
+ end
65
+
66
+
67
+ context "text/xml;charset=utf-8" do
68
+ before :each do
69
+ response = double(
70
+ :code => '200',
71
+ :body => 'body',
72
+ :headers => {"content-type" => ["text/xml;charset=utf-8"]},
73
+ :is_io? => false,
74
+ :is_error? => true,
75
+ :body_info_for_log => 'body',
76
+ :header_info_for_log => ""
77
+ )
78
+ @test_data[:response] = {:instance => response}
79
+ expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
80
+ once.with('body', {:encoding => 'UTF-8'})
81
+ end
82
+
83
+ it "works" do
84
+ @response_parser.execute(@test_data)
85
+ end
86
+ end
87
+
88
+
89
+ context "text/xml;charset=ISO" do
90
+ before :each do
91
+ response = double(
92
+ :code => '200',
93
+ :body => 'body',
94
+ :headers => {"content-type" => ["text/xml;charset=ISO"]},
95
+ :is_io? => false,
96
+ :is_error? => true,
97
+ :body_info_for_log => 'body',
98
+ :header_info_for_log => ""
99
+ )
100
+ @test_data[:response] = {:instance => response}
101
+ expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
102
+ once.with('body', {})
103
+ end
104
+
105
+ it "works" do
106
+ @response_parser.execute(@test_data)
107
+ end
108
+ end
109
+
110
+
111
+ context "text/xml" do
112
+ before :each do
113
+ response = double(
114
+ :code => '200',
115
+ :body => 'body',
116
+ :headers => {"content-type" => ["text/xml"]},
117
+ :is_io? => false,
118
+ :is_error? => true,
119
+ :body_info_for_log => 'body',
120
+ :header_info_for_log => ""
121
+ )
122
+ @test_data[:response] = {:instance => response}
123
+ expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
124
+ once.with('body', {})
125
+ end
126
+
127
+ it "works" do
128
+ @response_parser.execute(@test_data)
129
+ end
130
+ end
131
+
132
+
133
+ context "text/json;charset=ISO" do
134
+ before :each do
135
+ response = double(
136
+ :code => '200',
137
+ :body => 'body',
138
+ :headers => {"content-type" => ["text/json;charset=ISO"]},
139
+ :is_io? => false,
140
+ :is_error? => true,
141
+ :body_info_for_log => 'body',
142
+ :header_info_for_log => ""
143
+ )
144
+ @test_data[:response] = {:instance => response}
145
+ expect(RightScale::CloudApi::Parser::Json).to receive(:parse).\
146
+ once.with('body', {})
147
+ end
148
+
149
+ it "works" do
150
+ @response_parser.execute(@test_data)
151
+ end
152
+ end
153
+
154
+
155
+ context "text/json;charset=utf-8" do
156
+ before :each do
157
+ response = double(
158
+ :code => '200',
159
+ :body => 'body',
160
+ :headers => {"content-type" => ["text/json;charset=utf-8"]},
161
+ :is_io? => false,
162
+ :is_error? => true,
163
+ :body_info_for_log => 'body',
164
+ :header_info_for_log => ""
165
+ )
166
+ @test_data[:response] = {:instance => response}
167
+ expect(RightScale::CloudApi::Parser::Json).to receive(:parse).\
168
+ once.with('body', {:encoding => 'UTF-8'})
169
+ end
170
+
171
+ it "works" do
172
+ @response_parser.execute(@test_data)
173
+ end
174
+ end
175
+ end
176
+
177
+
178
+ context "parsing" do
179
+ context "XML, default" do
180
+ before :each do
181
+ @expectation = {'xml' => { 'a' => '1' }}
182
+ response = double(
183
+ :code => '200',
184
+ :body => @expectation._to_xml,
185
+ :headers => {"content-type" => ["text/xml"]},
186
+ :is_io? => false,
187
+ :is_error? => true,
188
+ :body_info_for_log => 'body',
189
+ :header_info_for_log => ""
190
+ )
191
+ @test_data[:response] = {:instance => response}
192
+ end
193
+
194
+ it "returns parsed response" do
195
+ @response_parser.execute(@test_data)
196
+ result = @test_data[:result]
197
+ expect(result).to be_a(Hash)
198
+ expect(result).to eq(@expectation)
199
+ end
200
+ end
201
+
202
+
203
+ context "XML, do not parse flag is set" do
204
+ before :each do
205
+ @expectation = {'xml' => { 'a' => '1' }}
206
+ response = double(
207
+ :code => '200',
208
+ :body => @expectation._to_xml,
209
+ :headers => {"content-type" => ["text/xml"]},
210
+ :is_io? => false,
211
+ :is_error? => true,
212
+ :body_info_for_log => 'body',
213
+ :header_info_for_log => ""
214
+ )
215
+ @test_data[:response] = {:instance => response}
216
+ @test_data[:options][:raw_response] = true
217
+ end
218
+
219
+ it "returns raw response" do
220
+ @response_parser.execute(@test_data)
221
+ result = @test_data[:result]
222
+ expect(result).to be_a(String)
223
+ expect(result).to eq(@expectation._to_xml)
224
+ end
225
+ end
226
+
227
+ end
228
+ end
@@ -0,0 +1,63 @@
1
+ #--
2
+ # Copyright (c) 2013 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
25
+
26
+ describe "RightScale::CloudApi::ResultWrapper" do
27
+ before (:each) do
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::INFO
30
+ @resultwrapper = RightScale::CloudApi::ResultWrapper.new
31
+ @test_data = {}
32
+ @test_data[:options] = { :user_agent => 'user_agent_data',
33
+ :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({:logger => logger})}
34
+ @test_data[:vars] = {}
35
+ @test_data[:callbacks] = {}
36
+
37
+ @parsed_response = "parsed_response"
38
+ @test_data[:result] = @parsed_response
39
+
40
+ @headers = { 'header1' => ['1'], 'header2' => ['2'] }
41
+ @response= generate_http_response(201, 'body', @headers)
42
+ @test_data[:response] = {:instance => @response, :parsed => @parsed_response }
43
+ allow(@resultwrapper).to receive(:log)
44
+ @test_data[:vars][:current_cache_key] = "cache_key"
45
+ @test_data[:vars][:cache] = {'cache_key' => { :key => 'old_cache_record_key', :record => 'haha' }}
46
+ @result = @resultwrapper.execute(@test_data)
47
+ end
48
+
49
+ context "metadata" do
50
+ it "looks like a parsed body object but responds to metadata" do
51
+ expect(@result).to eq 'parsed_response'
52
+ expect(@result).to be_a(String)
53
+ expect { @result.metadata }.to_not raise_error
54
+ end
55
+ it "contains the last respose headers and code" do
56
+ expect(@result.metadata[:headers]).to eq @headers
57
+ expect(@result.metadata[:code]).to eq @response.code
58
+ end
59
+ it "may contain the cache key" do
60
+ expect(@result.metadata[:cache]).to eq @test_data[:vars][:cache]
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,84 @@
1
+ #--
2
+ # Copyright (c) 2013 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 NONINFRINGEMENT.
18
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
25
+
26
+ describe "" do
27
+ before(:each) do
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::INFO
30
+ @retrymanager = RightScale::CloudApi::RetryManager.new
31
+ allow(@retrymanager).to receive(:log)
32
+ @request = double(:verb => 'get', :path => 'some/path', :body => 'body', :is_io? => false, :is_error? => false, :is_redirect? => false, :headers => {'header1' => 'val1', 'header2' => 'val2'})
33
+ @test_data = {
34
+ :options => { :user_agent => 'user_agent_data',
35
+ :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({}),
36
+ :retry => {
37
+ :count => 2,
38
+ :reiteration_time => 10,
39
+ :sleep_time => 0.2
40
+ },
41
+ },
42
+ :vars => { :system => {:block => 'block', :started_at => Time.now}},
43
+ :credentials => {},
44
+ :callbacks => {},
45
+ :connection => {:uri => double(:host => 'host.com', :port => '777', :scheme => 'scheme')},
46
+ :request => {:instance => @request}
47
+ }
48
+ @response = double(:code => '200', :body => 'body', :to_hash => {:code => '200', :body => 'body'})
49
+ @connection = double(:request => @response)
50
+ end
51
+
52
+ context "RightScale::CloudApi::RetryManager" do
53
+ it "works" do
54
+ # 1st run
55
+ @retrymanager.execute(@test_data)
56
+ expect(@test_data[:vars][:retry][:count]).to eq 0
57
+ expect(@test_data[:vars][:retry][:sleep_time]).to eq 0.2
58
+
59
+ # 2nd run, +1 count *2 sleep
60
+ @retrymanager.execute(@test_data)
61
+ expect(@test_data[:vars][:retry][:count]).to eq 1
62
+ expect(@test_data[:vars][:retry][:sleep_time]).to eq 0.4
63
+
64
+ # 3rd run, +1 count, *2 sleep
65
+ @retrymanager.execute(@test_data)
66
+ expect(@test_data[:vars][:retry][:count]).to eq 2
67
+ expect(@test_data[:vars][:retry][:sleep_time]).to eq 0.8
68
+
69
+ #4th run, case 1: default error
70
+ default_rm_error = "RetryManager: No more retries left."
71
+ expect do
72
+ @retrymanager.execute(@test_data)
73
+ end.to raise_error(RightScale::CloudApi::RetryManager::Error, default_rm_error)
74
+
75
+ #4th run, case 2: cloud_error + default error
76
+ http_error = 'Banana.'
77
+ expectation = "#{http_error}\n#{default_rm_error}"
78
+ @test_data[:vars][:retry][:http] = { :code => 777, :message => http_error }
79
+ expect do
80
+ @retrymanager.execute(@test_data)
81
+ end.to raise_error(RightScale::CloudApi::RetryManager::Error, expectation)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ require 'right_cloud_api_base'
2
+ require 'rspec'
3
+
4
+ # Generates a fake response object.
5
+ #
6
+ # @param [String] code HTTP response code
7
+ # @param [String] body HTTP response body
8
+ # @param [Hash] headers HTTP response code
9
+ # @param [HttpResponse] raw HTTP response object
10
+ #
11
+ # @return [RightScale::CloudApi::HTTPResponse]
12
+ #
13
+ def generate_http_response(code, body='body', headers={}, raw=nil)
14
+ RightScale::CloudApi::HTTPResponse.new(code.to_s, body, headers, raw)
15
+ end