revans_right_aws 2.0.1

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 (52) hide show
  1. data/.gemtest +0 -0
  2. data/History.txt +284 -0
  3. data/Manifest.txt +50 -0
  4. data/README.txt +167 -0
  5. data/Rakefile +110 -0
  6. data/lib/acf/right_acf_interface.rb +485 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +236 -0
  9. data/lib/acw/right_acw_interface.rb +249 -0
  10. data/lib/as/right_as_interface.rb +699 -0
  11. data/lib/awsbase/benchmark_fix.rb +39 -0
  12. data/lib/awsbase/right_awsbase.rb +978 -0
  13. data/lib/awsbase/support.rb +115 -0
  14. data/lib/ec2/right_ec2.rb +395 -0
  15. data/lib/ec2/right_ec2_ebs.rb +452 -0
  16. data/lib/ec2/right_ec2_images.rb +373 -0
  17. data/lib/ec2/right_ec2_instances.rb +755 -0
  18. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  19. data/lib/ec2/right_ec2_reserved_instances.rb +170 -0
  20. data/lib/ec2/right_ec2_security_groups.rb +277 -0
  21. data/lib/ec2/right_ec2_spot_instances.rb +399 -0
  22. data/lib/ec2/right_ec2_vpc.rb +571 -0
  23. data/lib/elb/right_elb_interface.rb +496 -0
  24. data/lib/rds/right_rds_interface.rb +998 -0
  25. data/lib/right_aws.rb +83 -0
  26. data/lib/s3/right_s3.rb +1126 -0
  27. data/lib/s3/right_s3_interface.rb +1199 -0
  28. data/lib/sdb/active_sdb.rb +1122 -0
  29. data/lib/sdb/right_sdb_interface.rb +721 -0
  30. data/lib/sqs/right_sqs.rb +388 -0
  31. data/lib/sqs/right_sqs_gen2.rb +343 -0
  32. data/lib/sqs/right_sqs_gen2_interface.rb +524 -0
  33. data/lib/sqs/right_sqs_interface.rb +594 -0
  34. data/test/acf/test_helper.rb +2 -0
  35. data/test/acf/test_right_acf.rb +138 -0
  36. data/test/ec2/test_helper.rb +2 -0
  37. data/test/ec2/test_right_ec2.rb +108 -0
  38. data/test/http_connection.rb +87 -0
  39. data/test/rds/test_helper.rb +2 -0
  40. data/test/rds/test_right_rds.rb +120 -0
  41. data/test/s3/test_helper.rb +2 -0
  42. data/test/s3/test_right_s3.rb +421 -0
  43. data/test/s3/test_right_s3_stubbed.rb +97 -0
  44. data/test/sdb/test_active_sdb.rb +357 -0
  45. data/test/sdb/test_helper.rb +3 -0
  46. data/test/sdb/test_right_sdb.rb +253 -0
  47. data/test/sqs/test_helper.rb +2 -0
  48. data/test/sqs/test_right_sqs.rb +291 -0
  49. data/test/sqs/test_right_sqs_gen2.rb +264 -0
  50. data/test/test_credentials.rb +37 -0
  51. data/test/ts_right_aws.rb +14 -0
  52. metadata +169 -0
@@ -0,0 +1,39 @@
1
+ #
2
+ # Copyright (c) 2007-2008 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
+
25
+
26
+ # A hack because there's a bug in add! in Benchmark::Tms
27
+ module Benchmark #:nodoc:
28
+ class Tms #:nodoc:
29
+ def add!(&blk)
30
+ t = Benchmark::measure(&blk)
31
+ @utime = utime + t.utime
32
+ @stime = stime + t.stime
33
+ @cutime = cutime + t.cutime
34
+ @cstime = cstime + t.cstime
35
+ @real = real + t.real
36
+ self
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,978 @@
1
+ #
2
+ # Copyright (c) 2007-2008 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
+ # Test
25
+ module RightAws
26
+ # require 'md5'
27
+ require 'digest/md5'
28
+ require 'pp'
29
+
30
+ class AwsUtils #:nodoc:
31
+ @@digest1 = OpenSSL::Digest::Digest.new("sha1")
32
+ @@digest256 = nil
33
+ if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
34
+ @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
35
+ end
36
+
37
+ def self.utc_iso8601(time)
38
+ if time.is_a?(Fixnum) then time = Time::at(time)
39
+ elsif time.is_a?(String) then time = Time::parse(time)
40
+ end
41
+ time.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
42
+ end
43
+
44
+ def self.sign(aws_secret_access_key, auth_string)
45
+ Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
46
+ end
47
+
48
+ # Escape a string accordingly Amazon rulles
49
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
50
+ def self.amz_escape(param)
51
+ param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
52
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
53
+ end
54
+ end
55
+
56
+ # Set a timestamp and a signature version
57
+ def self.fix_service_params(service_hash, signature)
58
+ service_hash["Timestamp"] ||= utc_iso8601(Time.now) unless service_hash["Expires"]
59
+ service_hash["SignatureVersion"] = signature
60
+ service_hash
61
+ end
62
+
63
+ # Signature Version 0
64
+ # A deprecated guy (should work till septemper 2009)
65
+ def self.sign_request_v0(aws_secret_access_key, service_hash)
66
+ fix_service_params(service_hash, '0')
67
+ string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
68
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
69
+ service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
70
+ end
71
+
72
+ # Signature Version 1
73
+ # Another deprecated guy (should work till septemper 2009)
74
+ def self.sign_request_v1(aws_secret_access_key, service_hash)
75
+ fix_service_params(service_hash, '1')
76
+ string_to_sign = service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
77
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
78
+ service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
79
+ end
80
+
81
+ # Signature Version 2
82
+ # EC2, SQS and SDB requests must be signed by this guy.
83
+ # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
84
+ # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
85
+ def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
86
+ fix_service_params(service_hash, '2')
87
+ # select a signing method (make an old openssl working with sha1)
88
+ # make 'HmacSHA256' to be a default one
89
+ service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
90
+ service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
91
+ # select a digest
92
+ digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
93
+ # form string to sign
94
+ canonical_string = service_hash.keys.sort.map do |key|
95
+ "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
96
+ end.join('&')
97
+ string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
98
+ # sign the string
99
+ signature = amz_escape(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
100
+ "#{canonical_string}&Signature=#{signature}"
101
+ end
102
+
103
+ # From Amazon's SQS Dev Guide, a brief description of how to escape:
104
+ # "URL encode the computed signature and other query parameters as specified in
105
+ # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space
106
+ # by Sun Java classes that perform URL decoding, make sure to encode the + character
107
+ # although it is not required by RFC1738."
108
+ # Avoid using CGI::escape to escape URIs.
109
+ # CGI::escape will escape characters in the protocol, host, and port
110
+ # sections of the URI. Only target chars in the query
111
+ # string should be escaped.
112
+ def self.URLencode(raw)
113
+ e = URI.escape(raw)
114
+ e.gsub(/\+/, "%2b")
115
+ end
116
+
117
+ def self.allow_only(allowed_keys, params)
118
+ bogus_args = []
119
+ params.keys.each {|p| bogus_args.push(p) unless allowed_keys.include?(p) }
120
+ raise AwsError.new("The following arguments were given but are not legal for the function call #{caller_method}: #{bogus_args.inspect}") if bogus_args.length > 0
121
+ end
122
+
123
+ def self.mandatory_arguments(required_args, params)
124
+ rargs = required_args.dup
125
+ params.keys.each {|p| rargs.delete(p)}
126
+ raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
127
+ end
128
+
129
+ def self.caller_method
130
+ caller[1]=~/`(.*?)'/
131
+ $1
132
+ end
133
+
134
+ def self.split_items_and_params(array)
135
+ items = Array(array).flatten.compact
136
+ params = items.last.kind_of?(Hash) ? items.pop : {}
137
+ [items, params]
138
+ end
139
+ end
140
+
141
+ class AwsBenchmarkingBlock #:nodoc:
142
+ attr_accessor :xml, :service
143
+ def initialize
144
+ # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
145
+ @service = Benchmark::Tms.new()
146
+ # Benchmark::Tms instance for XML parsing benchmarking.
147
+ @xml = Benchmark::Tms.new()
148
+ end
149
+ end
150
+
151
+ class AwsNoChange < RuntimeError
152
+ end
153
+
154
+ class RightAwsBase
155
+
156
+ # Amazon HTTP Error handling
157
+
158
+ # Text, if found in an error message returned by AWS, indicates that this may be a transient
159
+ # error. Transient errors are automatically retried with exponential back-off.
160
+ AMAZON_PROBLEMS = [ 'internal service error',
161
+ 'is currently unavailable',
162
+ 'no response from',
163
+ 'Please try again',
164
+ 'InternalError',
165
+ 'Internal Server Error',
166
+ 'ServiceUnavailable', #from SQS docs
167
+ 'Unavailable',
168
+ 'This application is not currently available',
169
+ 'InsufficientInstanceCapacity'
170
+ ]
171
+ @@amazon_problems = AMAZON_PROBLEMS
172
+ # Returns a list of Amazon service responses which are known to be transient problems.
173
+ # We have to re-request if we get any of them, because the problem will probably disappear.
174
+ # By default this method returns the same value as the AMAZON_PROBLEMS const.
175
+ def self.amazon_problems
176
+ @@amazon_problems
177
+ end
178
+
179
+ # Sets the list of Amazon side problems. Use in conjunction with the
180
+ # getter to append problems.
181
+ def self.amazon_problems=(problems_list)
182
+ @@amazon_problems = problems_list
183
+ end
184
+
185
+ end
186
+
187
+ module RightAwsBaseInterface
188
+ DEFAULT_SIGNATURE_VERSION = '2'
189
+
190
+ @@caching = false
191
+ def self.caching
192
+ @@caching
193
+ end
194
+ def self.caching=(caching)
195
+ @@caching = caching
196
+ end
197
+
198
+ # Current aws_access_key_id
199
+ attr_reader :aws_access_key_id
200
+ # Current aws_secret_access_key
201
+ attr_reader :aws_secret_access_key
202
+ # Last HTTP request object
203
+ attr_reader :last_request
204
+ # Last HTTP response object
205
+ attr_reader :last_response
206
+ # Last AWS errors list (used by AWSErrorHandler)
207
+ attr_accessor :last_errors
208
+ # Last AWS request id (used by AWSErrorHandler)
209
+ attr_accessor :last_request_id
210
+ # Logger object
211
+ attr_accessor :logger
212
+ # Initial params hash
213
+ attr_accessor :params
214
+ # RightHttpConnection instance
215
+ attr_reader :connection
216
+ # Cache
217
+ attr_reader :cache
218
+ # Signature version (all services except s3)
219
+ attr_reader :signature_version
220
+
221
+ def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
222
+ @params = params
223
+ # If one defines EC2_URL he may forget to use a single slash as an "empty service" path.
224
+ # Amazon does not like this therefore add this bad boy if he is missing...
225
+ service_info[:default_service] = '/' if service_info[:default_service].blank?
226
+ raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
227
+ if aws_access_key_id.blank? || aws_secret_access_key.blank?
228
+ @aws_access_key_id = aws_access_key_id
229
+ @aws_secret_access_key = aws_secret_access_key
230
+ # if the endpoint was explicitly defined - then use it
231
+ if @params[:endpoint_url]
232
+ @params[:server] = URI.parse(@params[:endpoint_url]).host
233
+ @params[:port] = URI.parse(@params[:endpoint_url]).port
234
+ @params[:service] = URI.parse(@params[:endpoint_url]).path
235
+ # make sure the 'service' path is not empty
236
+ @params[:service] = service_info[:default_service] if @params[:service].blank?
237
+ @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
238
+ @params[:region] = nil
239
+ else
240
+ @params[:server] ||= service_info[:default_host]
241
+ @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
242
+ @params[:port] ||= service_info[:default_port]
243
+ @params[:service] ||= service_info[:default_service]
244
+ @params[:protocol] ||= service_info[:default_protocol]
245
+ end
246
+ # @params[:multi_thread] ||= defined?(AWS_DAEMON)
247
+ @params[:connections] ||= :shared # || :dedicated
248
+ @params[:max_connections] ||= 10
249
+ @params[:connection_lifetime] ||= 20*60
250
+ @params[:api_version] ||= service_info[:default_api_version]
251
+ @logger = @params[:logger]
252
+ @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
253
+ @logger = Logger.new(STDOUT) if !@logger
254
+ @logger.info "New #{self.class.name} using #{@params[:connections]} connections mode"
255
+ @error_handler = nil
256
+ @cache = {}
257
+ @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
258
+ end
259
+
260
+ def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil )
261
+ case signature_version.to_s
262
+ when '0' then AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
263
+ when '1' then AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
264
+ when '2' then AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
265
+ else raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
266
+ end
267
+ end
268
+
269
+ # Returns +true+ if the describe_xxx responses are being cached
270
+ def caching?
271
+ @params.key?(:cache) ? @params[:cache] : @@caching
272
+ end
273
+
274
+ # Check if the aws function response hits the cache or not.
275
+ # If the cache hits:
276
+ # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+.
277
+ # - returnes parsed response from the cache if it exists or +true+ otherwise.
278
+ # If the cache miss or the caching is off then returns +false+.
279
+ def cache_hits?(function, response, do_raise=:raise)
280
+ result = false
281
+ if caching?
282
+ function = function.to_sym
283
+ # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
284
+ # feb 04, 2009 (load balancer uses 'RequestId' hence use 'i' modifier to hit it also)
285
+ response = response.sub(%r{<requestId>.+?</requestId>}i, '')
286
+ response_md5 = MD5.md5(response).to_s
287
+ # check for changes
288
+ unless @cache[function] && @cache[function][:response_md5] == response_md5
289
+ # well, the response is new, reset cache data
290
+ update_cache(function, {:response_md5 => response_md5,
291
+ :timestamp => Time.now,
292
+ :hits => 0,
293
+ :parsed => nil})
294
+ else
295
+ # aha, cache hits, update the data and throw an exception if needed
296
+ @cache[function][:hits] += 1
297
+ if do_raise == :raise
298
+ raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+
299
+ "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+
300
+ "hits: #{@cache[function][:hits]}.")
301
+ else
302
+ result = @cache[function][:parsed] || true
303
+ end
304
+ end
305
+ end
306
+ result
307
+ end
308
+
309
+ def update_cache(function, hash)
310
+ (@cache[function.to_sym] ||= {}).merge!(hash) if caching?
311
+ end
312
+
313
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
314
+ raise if $!.is_a?(AwsNoChange)
315
+ AwsError::on_aws_exception(self, options)
316
+ end
317
+
318
+ # # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
319
+ # def multi_thread
320
+ # @params[:multi_thread]
321
+ # end
322
+
323
+ # ACF, AMS, EC2, LBS and SDB uses this guy
324
+ # SQS and S3 use their own methods
325
+ def generate_request_impl(verb, action, options={}) #:nodoc:
326
+ # Form a valid http verb: 'GET' or 'POST' (all the other are not supported now)
327
+ http_verb = verb.to_s.upcase
328
+ # remove empty keys from request options
329
+ options.delete_if { |key, value| value.nil? }
330
+ # prepare service data
331
+ service_hash = {"Action" => action,
332
+ "AWSAccessKeyId" => @aws_access_key_id,
333
+ "Version" => @params[:api_version] }
334
+ service_hash.merge!(options)
335
+ # Sign request options
336
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:server], @params[:service])
337
+ # Use POST if the length of the query string is too large
338
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
339
+ if http_verb != 'POST' && service_params.size > 2000
340
+ http_verb = 'POST'
341
+ if signature_version == '2'
342
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:server], @params[:service])
343
+ end
344
+ end
345
+ # create a request
346
+ case http_verb
347
+ when 'GET'
348
+ request = Net::HTTP::Get.new("#{@params[:service]}?#{service_params}")
349
+ when 'POST'
350
+ request = Net::HTTP::Post.new(@params[:service])
351
+ request.body = service_params
352
+ request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
353
+ else
354
+ raise "Unsupported HTTP verb #{verb.inspect}!"
355
+ end
356
+ # prepare output hash
357
+ { :request => request,
358
+ :server => @params[:server],
359
+ :port => @params[:port],
360
+ :protocol => @params[:protocol] }
361
+ end
362
+
363
+ def get_connection(aws_service, request) #:nodoc
364
+ server_url = "#{request[:protocol]}://#{request[:server]}:#{request[:port]}}"
365
+ #
366
+ case @params[:connections].to_s
367
+ when 'dedicated'
368
+ @connections_storage ||= {}
369
+ else # 'dedicated'
370
+ @connections_storage = (Thread.current[aws_service] ||= {})
371
+ end
372
+ #
373
+ @connections_storage[server_url] ||= {}
374
+ @connections_storage[server_url][:last_used_at] = Time.now
375
+ @connections_storage[server_url][:connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
376
+ # keep X most recent connections (but were used not far than Y minutes ago)
377
+ connections = 0
378
+ @connections_storage.to_a.sort{|i1, i2| i2[1][:last_used_at] <=> i1[1][:last_used_at]}.to_a.each do |i|
379
+ if i[0] != server_url && (@params[:max_connections] <= connections || i[1][:last_used_at] < Time.now - @params[:connection_lifetime])
380
+ # delete the connection from the list
381
+ @connections_storage.delete(i[0])
382
+ # then finish it
383
+ i[1][:connection].finish((@params[:max_connections] <= connections) ? "out-of-limit" : "out-of-date") rescue nil
384
+ else
385
+ connections += 1
386
+ end
387
+ end
388
+ @connections_storage[server_url][:connection]
389
+ end
390
+
391
+ # All services uses this guy.
392
+ def request_info_impl(aws_service, benchblock, request, parser, &block) #:nodoc:
393
+ @connection = get_connection(aws_service, request)
394
+ @last_request = request[:request]
395
+ @last_response = nil
396
+ response = nil
397
+ blockexception = nil
398
+
399
+ if(block != nil)
400
+ # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
401
+ # an exception may get thrown in the block body (which is high-level
402
+ # code either here or in the application) but gets caught in the
403
+ # low-level code of HttpConnection. The solution is not to let any
404
+ # exception escape the block that we pass to HttpConnection::request.
405
+ # Exceptions can originate from code directly in the block, or from user
406
+ # code called in the other block which is passed to response.read_body.
407
+ benchblock.service.add! do
408
+ responsehdr = @connection.request(request) do |response|
409
+ #########
410
+ begin
411
+ @last_response = response
412
+ if response.is_a?(Net::HTTPSuccess)
413
+ @error_handler = nil
414
+ response.read_body(&block)
415
+ else
416
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
417
+ check_result = @error_handler.check(request)
418
+ if check_result
419
+ @error_handler = nil
420
+ return check_result
421
+ end
422
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
423
+ end
424
+ rescue Exception => e
425
+ blockexception = e
426
+ end
427
+ end
428
+ #########
429
+
430
+ #OK, now we are out of the block passed to the lower level
431
+ if(blockexception)
432
+ raise blockexception
433
+ end
434
+ benchblock.xml.add! do
435
+ parser.parse(responsehdr)
436
+ end
437
+ return parser.result
438
+ end
439
+ else
440
+ benchblock.service.add!{ response = @connection.request(request) }
441
+ # check response for errors...
442
+ @last_response = response
443
+ if response.is_a?(Net::HTTPSuccess)
444
+ @error_handler = nil
445
+ benchblock.xml.add! { parser.parse(response) }
446
+ return parser.result
447
+ else
448
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
449
+ check_result = @error_handler.check(request)
450
+ if check_result
451
+ @error_handler = nil
452
+ return check_result
453
+ end
454
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
455
+ end
456
+ end
457
+ rescue
458
+ @error_handler = nil
459
+ raise
460
+ end
461
+
462
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
463
+ # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
464
+ # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
465
+ # If the caching is enabled and hit then throw AwsNoChange.
466
+ # P.S. caching works for the whole images list only! (when the list param is blank)
467
+ # check cache
468
+ response, params = request_info(link, RightDummyParser.new)
469
+ cache_hits?(method.to_sym, response.body) if use_cache
470
+ parser = parser_class.new(:logger => @logger)
471
+ benchblock.xml.add!{ parser.parse(response, params) }
472
+ result = block_given? ? yield(parser) : parser.result
473
+ # update parsed data
474
+ update_cache(method.to_sym, :parsed => result) if use_cache
475
+ result
476
+ end
477
+
478
+ # Returns Amazons request ID for the latest request
479
+ def last_request_id
480
+ @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}i] && $1
481
+ end
482
+
483
+ # Format array of items into Amazons handy hash ('?' is a place holder):
484
+ #
485
+ # amazonize_list('Item', ['a', 'b', 'c']) =>
486
+ # { 'Item.1' => 'a', 'Item.2' => 'b', 'Item.3' => 'c' }
487
+ #
488
+ # amazonize_list('Item.?.instance', ['a', 'c']) #=>
489
+ # { 'Item.1.instance' => 'a', 'Item.2.instance' => 'c' }
490
+ #
491
+ # amazonize_list(['Item.?.Name', 'Item.?.Value'], {'A' => 'a', 'B' => 'b'}) #=>
492
+ # { 'Item.1.Name' => 'A', 'Item.1.Value' => 'a',
493
+ # 'Item.2.Name' => 'B', 'Item.2.Value' => 'b' }
494
+ #
495
+ # amazonize_list(['Item.?.Name', 'Item.?.Value'], [['A','a'], ['B','b']]) #=>
496
+ # { 'Item.1.Name' => 'A', 'Item.1.Value' => 'a',
497
+ # 'Item.2.Name' => 'B', 'Item.2.Value' => 'b' }
498
+ #
499
+ # amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], {'A' => ['aa','ab'], 'B' => ['ba','bb']}) #=>
500
+ # amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], [['A',['aa','ab']], ['B',['ba','bb']]]) #=>
501
+ # {"Filter.1.Key"=>"A",
502
+ # "Filter.1.Value.1"=>"aa",
503
+ # "Filter.1.Value.2"=>"ab",
504
+ # "Filter.2.Key"=>"B",
505
+ # "Filter.2.Value.1"=>"ba",
506
+ # "Filter.2.Value.2"=>"bb"}
507
+ def amazonize_list(masks, list) #:nodoc:
508
+ groups = {}
509
+ Array(list).each_with_index do |list_item, i|
510
+ Array(masks).each_with_index do |mask, mask_idx|
511
+ key = mask[/\?/] ? mask.dup : mask.dup + '.?'
512
+ key.sub!('?', (i+1).to_s)
513
+ value = Array(list_item)[mask_idx]
514
+ if value.is_a?(Array)
515
+ groups.merge!(amazonize_list(key, value))
516
+ else
517
+ groups[key] = value
518
+ end
519
+ end
520
+ end
521
+ groups
522
+ end
523
+
524
+ BLOCK_DEVICE_KEY_MAPPING = { # :nodoc:
525
+ :device_name => 'DeviceName',
526
+ :virtual_name => 'VirtualName',
527
+ :no_device => 'NoDevice',
528
+ :ebs_snapshot_id => 'Ebs.SnapshotId',
529
+ :ebs_volume_size => 'Ebs.VolumeSize',
530
+ :ebs_delete_on_termination => 'Ebs.DeleteOnTermination' }
531
+
532
+ def amazonize_block_device_mappings(block_device_mappings, key = 'BlockDeviceMapping') # :nodoc:
533
+ result = {}
534
+ unless block_device_mappings.blank?
535
+ block_device_mappings = [block_device_mappings] unless block_device_mappings.is_a?(Array)
536
+ block_device_mappings.each_with_index do |b, idx|
537
+ BLOCK_DEVICE_KEY_MAPPING.each do |local_name, remote_name|
538
+ value = b[local_name]
539
+ case local_name
540
+ when :no_device then value = value ? '' : nil # allow to pass :no_device as boolean
541
+ end
542
+ result["#{key}.#{idx+1}.#{remote_name}"] = value unless value.nil?
543
+ end
544
+ end
545
+ end
546
+ result
547
+ end
548
+
549
+ end
550
+
551
+
552
+ # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
553
+ # web services raise this type of error.
554
+ # Attribute inherited by RuntimeError:
555
+ # message - the text of the error, generally as returned by AWS in its XML response.
556
+ class AwsError < RuntimeError
557
+
558
+ # either an array of errors where each item is itself an array of [code, message]),
559
+ # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
560
+ attr_reader :errors
561
+
562
+ # Request id (if exists)
563
+ attr_reader :request_id
564
+
565
+ # Response HTTP error code
566
+ attr_reader :http_code
567
+
568
+ def initialize(errors=nil, http_code=nil, request_id=nil)
569
+ @errors = errors
570
+ @request_id = request_id
571
+ @http_code = http_code
572
+ super(@errors.is_a?(Array) ? @errors.map{|code, msg| "#{code}: #{msg}"}.join("; ") : @errors.to_s)
573
+ end
574
+
575
+ # Does any of the error messages include the regexp +pattern+?
576
+ # Used to determine whether to retry request.
577
+ def include?(pattern)
578
+ if @errors.is_a?(Array)
579
+ @errors.each{ |code, msg| return true if code =~ pattern }
580
+ else
581
+ return true if @errors_str =~ pattern
582
+ end
583
+ false
584
+ end
585
+
586
+ # Generic handler for AwsErrors. +aws+ is the RightAws::S3, RightAws::EC2, or RightAws::SQS
587
+ # object that caused the exception (it must provide last_request and last_response). Supported
588
+ # boolean options are:
589
+ # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
590
+ # * <tt>:puts</tt> do a "puts" of the error
591
+ # * <tt>:raise</tt> re-raise the error after logging
592
+ def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
593
+ # Only log & notify if not user error
594
+ if !options[:raise] || system_error?($!)
595
+ error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
596
+ puts error_text if options[:puts]
597
+ # Log the error
598
+ if options[:log]
599
+ request = aws.last_request ? aws.last_request.path : '-none-'
600
+ response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
601
+ aws.logger.error error_text
602
+ aws.logger.error "Request was: #{request}"
603
+ aws.logger.error "Response was: #{response}"
604
+ end
605
+ end
606
+ raise if options[:raise] # re-raise an exception
607
+ return nil
608
+ end
609
+
610
+ # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
611
+ # Used to force logging.
612
+ def self.system_error?(e)
613
+ !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
614
+ end
615
+
616
+ end
617
+
618
+
619
+ class AWSErrorHandler
620
+ # 0-100 (%)
621
+ DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
622
+
623
+ @@reiteration_start_delay = 0.2
624
+ def self.reiteration_start_delay
625
+ @@reiteration_start_delay
626
+ end
627
+ def self.reiteration_start_delay=(reiteration_start_delay)
628
+ @@reiteration_start_delay = reiteration_start_delay
629
+ end
630
+
631
+ @@reiteration_time = 5
632
+ def self.reiteration_time
633
+ @@reiteration_time
634
+ end
635
+ def self.reiteration_time=(reiteration_time)
636
+ @@reiteration_time = reiteration_time
637
+ end
638
+
639
+ @@close_on_error = true
640
+ def self.close_on_error
641
+ @@close_on_error
642
+ end
643
+ def self.close_on_error=(close_on_error)
644
+ @@close_on_error = close_on_error
645
+ end
646
+
647
+ @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
648
+ def self.close_on_4xx_probability
649
+ @@close_on_4xx_probability
650
+ end
651
+ def self.close_on_4xx_probability=(close_on_4xx_probability)
652
+ @@close_on_4xx_probability = close_on_4xx_probability
653
+ end
654
+
655
+ # params:
656
+ # :reiteration_time
657
+ # :errors_list
658
+ # :close_on_error = true | false
659
+ # :close_on_4xx_probability = 1-100
660
+ def initialize(aws, parser, params={}) #:nodoc:
661
+ @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
662
+ @parser = parser # parser to parse Amazon response
663
+ @started_at = Time.now
664
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
665
+ @errors_list = params[:errors_list] || []
666
+ @reiteration_delay = @@reiteration_start_delay
667
+ @retries = 0
668
+ # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
669
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
670
+ @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
671
+ end
672
+
673
+ # Returns false if
674
+ def check(request) #:nodoc:
675
+ result = false
676
+ error_found = false
677
+ redirect_detected= false
678
+ error_match = nil
679
+ last_errors_text = ''
680
+ response = @aws.last_response
681
+ # log error
682
+ request_text_data = "#{request[:protocol]}://#{request[:server]}:#{request[:port]}#{request[:request].path}"
683
+ # is this a redirect?
684
+ # yes!
685
+ if response.is_a?(Net::HTTPRedirection)
686
+ redirect_detected = true
687
+ else
688
+ # no, it's an error ...
689
+ @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
690
+ @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
691
+ end
692
+
693
+ # Extract error/redirection message from the response body
694
+ # Amazon claims that a redirection must have a body but somethimes it is nil....
695
+ if response.body && response.body[/^(<\?xml|<ErrorResponse)/]
696
+ error_parser = RightErrorResponseParser.new
697
+ @aws.class.bench_xml.add! do
698
+ error_parser.parse(response.body)
699
+ end
700
+ @aws.last_errors = error_parser.errors
701
+ @aws.last_request_id = error_parser.requestID
702
+ last_errors_text = @aws.last_errors.flatten.join("\n")
703
+ else
704
+ @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
705
+ @aws.last_request_id = '-undefined-'
706
+ last_errors_text = response.message
707
+ end
708
+
709
+ # Ok, it is a redirect, find the new destination location
710
+ if redirect_detected
711
+ location = response['location']
712
+ # ... log information and ...
713
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
714
+ @aws.logger.info(" Old location: #{request_text_data}")
715
+ @aws.logger.info(" New location: #{location}")
716
+ # ... fix the connection data
717
+ request[:server] = URI.parse(location).host
718
+ request[:protocol] = URI.parse(location).scheme
719
+ request[:port] = URI.parse(location).port
720
+ else
721
+ # Not a redirect but an error: try to find the error in our list
722
+ @errors_list.each do |error_to_find|
723
+ if last_errors_text[/#{error_to_find}/i]
724
+ error_found = true
725
+ error_match = error_to_find
726
+ @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
727
+ break
728
+ end
729
+ end
730
+ end
731
+
732
+ # check the time has gone from the first error come
733
+ if redirect_detected || error_found
734
+ # Close the connection to the server and recreate a new one.
735
+ # It may have a chance that one server is a semi-down and reconnection
736
+ # will help us to connect to the other server
737
+ if !redirect_detected && @close_on_error
738
+ @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
739
+ end
740
+
741
+ if (Time.now < @stop_at)
742
+ @retries += 1
743
+ unless redirect_detected
744
+ @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
745
+ sleep @reiteration_delay
746
+ @reiteration_delay *= 2
747
+
748
+ # Always make sure that the fp is set to point to the beginning(?)
749
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
750
+ if(request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
751
+ begin
752
+ request[:request].body_stream.pos = 0
753
+ rescue Exception => e
754
+ @logger.warn("Retry may fail due to unable to reset the file pointer" +
755
+ " -- #{self.class.name} : #{e.inspect}")
756
+ end
757
+ end
758
+ else
759
+ @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
760
+ end
761
+ result = @aws.request_info(request, @parser)
762
+ else
763
+ @aws.logger.warn("##### Ooops, time is over... ####")
764
+ end
765
+ # aha, this is unhandled error:
766
+ elsif @close_on_error
767
+ # Is this a 5xx error ?
768
+ if @aws.last_response.code.to_s[/^5\d\d$/]
769
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
770
+ # Is this a 4xx error ?
771
+ elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
772
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
773
+ "probability: #{@close_on_4xx_probability}%"
774
+ end
775
+ end
776
+ result
777
+ end
778
+
779
+ end
780
+
781
+
782
+ #-----------------------------------------------------------------
783
+
784
+ class RightSaxParserCallback #:nodoc:
785
+ def self.include_callback
786
+ include XML::SaxParser::Callbacks
787
+ end
788
+ def initialize(right_aws_parser)
789
+ @right_aws_parser = right_aws_parser
790
+ end
791
+ def on_start_element(name, attr_hash)
792
+ @right_aws_parser.tag_start(name, attr_hash)
793
+ end
794
+ def on_characters(chars)
795
+ @right_aws_parser.text(chars)
796
+ end
797
+ def on_end_element(name)
798
+ @right_aws_parser.tag_end(name)
799
+ end
800
+ def on_start_document; end
801
+ def on_comment(msg); end
802
+ def on_processing_instruction(target, data); end
803
+ def on_cdata_block(cdata); end
804
+ def on_end_document; end
805
+ end
806
+
807
+ class RightAWSParser #:nodoc:
808
+ # default parsing library
809
+ DEFAULT_XML_LIBRARY = 'rexml'
810
+ # a list of supported parsers
811
+ @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml']
812
+
813
+ @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml'
814
+ def self.xml_lib
815
+ @@xml_lib
816
+ end
817
+ def self.xml_lib=(new_lib_name)
818
+ @@xml_lib = new_lib_name
819
+ end
820
+
821
+ attr_accessor :result
822
+ attr_reader :xmlpath
823
+ attr_accessor :xml_lib
824
+ attr_reader :full_tag_name
825
+ attr_reader :tag
826
+
827
+ def initialize(params={})
828
+ @xmlpath = ''
829
+ @full_tag_name = ''
830
+ @result = false
831
+ @text = ''
832
+ @tag = ''
833
+ @xml_lib = params[:xml_lib] || @@xml_lib
834
+ @logger = params[:logger]
835
+ reset
836
+ end
837
+ def tag_start(name, attributes)
838
+ @text = ''
839
+ @tag = name
840
+ @full_tag_name += @full_tag_name.empty? ? name : "/#{name}"
841
+ tagstart(name, attributes)
842
+ @xmlpath = @full_tag_name
843
+ end
844
+ def tag_end(name)
845
+ @xmlpath = @full_tag_name[/^(.*?)\/?#{name}$/] && $1
846
+ tagend(name)
847
+ @full_tag_name = @xmlpath
848
+ end
849
+ def text(text)
850
+ @text += text
851
+ tagtext(text)
852
+ end
853
+ # Parser method.
854
+ # Params:
855
+ # xml_text - xml message text(String) or Net:HTTPxxx instance (response)
856
+ # params[:xml_lib] - library name: 'rexml' | 'libxml'
857
+ def parse(xml_text, params={})
858
+ # Get response body
859
+ xml_text = xml_text.body unless xml_text.is_a?(String)
860
+ @xml_lib = params[:xml_lib] || @xml_lib
861
+ # check that we had no problems with this library otherwise use default
862
+ @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib)
863
+ # load xml library
864
+ if @xml_lib=='libxml' && !defined?(XML::SaxParser)
865
+ begin
866
+ require 'xml/libxml'
867
+ # is it new ? - Setup SaxParserCallback
868
+ if XML::Parser::VERSION >= '0.5.1.0'
869
+ RightSaxParserCallback.include_callback
870
+ end
871
+ rescue LoadError => e
872
+ @@supported_xml_libs.delete(@xml_lib)
873
+ @xml_lib = DEFAULT_XML_LIBRARY
874
+ if @logger
875
+ @logger.error e.inspect
876
+ @logger.error e.backtrace
877
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
878
+ end
879
+ end
880
+ end
881
+ # Parse the xml text
882
+ case @xml_lib
883
+ when 'libxml'
884
+ if XML::Parser::VERSION >= '0.9.9'
885
+ # avoid warning on every usage
886
+ xml = XML::SaxParser.string(xml_text)
887
+ else
888
+ xml = XML::SaxParser.new
889
+ xml.string = xml_text
890
+ end
891
+ # check libxml-ruby version
892
+ if XML::Parser::VERSION >= '0.5.1.0'
893
+ xml.callbacks = RightSaxParserCallback.new(self)
894
+ else
895
+ xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
896
+ xml.on_characters{ |text| self.text(text)}
897
+ xml.on_end_element{ |name| self.tag_end(name)}
898
+ end
899
+ xml.parse
900
+ else
901
+ REXML::Document.parse_stream(xml_text, self)
902
+ end
903
+ end
904
+ # Parser must have a lots of methods
905
+ # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb)
906
+ # We dont need most of them in RightAWSParser and method_missing helps us
907
+ # to skip their definition
908
+ def method_missing(method, *params)
909
+ # if the method is one of known - just skip it ...
910
+ return if [:comment, :attlistdecl, :notationdecl, :elementdecl,
911
+ :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction,
912
+ :doctype].include?(method)
913
+ # ... else - call super to raise an exception
914
+ super(method, params)
915
+ end
916
+ # the functions to be overriden by children (if nessesery)
917
+ def reset ; end
918
+ def tagstart(name, attributes); end
919
+ def tagend(name) ; end
920
+ def tagtext(text) ; end
921
+ end
922
+
923
+ #-----------------------------------------------------------------
924
+ # PARSERS: Errors
925
+ #-----------------------------------------------------------------
926
+
927
+ #<Error>
928
+ # <Code>TemporaryRedirect</Code>
929
+ # <Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message>
930
+ # <RequestId>FD8D5026D1C5ABA3</RequestId>
931
+ # <Endpoint>bucket-for-k.s3-external-3.amazonaws.com</Endpoint>
932
+ # <HostId>ItJy8xPFPli1fq/JR3DzQd3iDvFCRqi1LTRmunEdM1Uf6ZtW2r2kfGPWhRE1vtaU</HostId>
933
+ # <Bucket>bucket-for-k</Bucket>
934
+ #</Error>
935
+
936
+ class RightErrorResponseParser < RightAWSParser #:nodoc:
937
+ attr_accessor :errors # array of hashes: error/message
938
+ attr_accessor :requestID
939
+ # attr_accessor :endpoint, :host_id, :bucket
940
+ def tagend(name)
941
+ case name
942
+ when 'RequestID' ; @requestID = @text
943
+ when 'Code' ; @code = @text
944
+ when 'Message' ; @message = @text
945
+ # when 'Endpoint' ; @endpoint = @text
946
+ # when 'HostId' ; @host_id = @text
947
+ # when 'Bucket' ; @bucket = @text
948
+ when 'Error' ; @errors << [ @code, @message ]
949
+ end
950
+ end
951
+ def reset
952
+ @errors = []
953
+ end
954
+ end
955
+
956
+ # Dummy parser - does nothing
957
+ # Returns the original params back
958
+ class RightDummyParser # :nodoc:
959
+ attr_accessor :result
960
+ def parse(response, params={})
961
+ @result = [response, params]
962
+ end
963
+ end
964
+
965
+ class RightHttp2xxParser < RightAWSParser # :nodoc:
966
+ def parse(response)
967
+ @result = response.is_a?(Net::HTTPSuccess)
968
+ end
969
+ end
970
+
971
+ class RightBoolResponseParser < RightAWSParser #:nodoc:
972
+ def tagend(name)
973
+ @result = (@text=='true') if name == 'return'
974
+ end
975
+ end
976
+
977
+ end
978
+