right_cloud_api_base 0.1.0

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