revans_right_aws 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+