steamcannon-aws 2.3.26.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/aws.rb ADDED
@@ -0,0 +1,31 @@
1
+
2
+ require 'benchmark'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'time'
6
+ require "cgi"
7
+ require "base64"
8
+ require "rexml/document"
9
+ require "openssl"
10
+ require "digest/sha1"
11
+
12
+ require 'rubygems'
13
+ require 'right_http_connection'
14
+
15
+ $:.unshift(File.dirname(__FILE__))
16
+ require 'awsbase/benchmark_fix'
17
+ require 'awsbase/support'
18
+ require 'awsbase/right_awsbase'
19
+ require 'awsbase/aws_response_array'
20
+ require 'ec2/right_ec2'
21
+ require 'ec2/right_mon_interface'
22
+ require 's3/right_s3_interface'
23
+ require 's3/right_s3'
24
+ require 'sqs/right_sqs_interface'
25
+ require 'sqs/right_sqs'
26
+ require 'sdb/right_sdb_interface'
27
+ require 'acf/right_acf_interface'
28
+ require 'elb/elb_interface'
29
+ require 'rds/rds'
30
+ require 'iam/iam'
31
+
@@ -0,0 +1,30 @@
1
+ module Aws
2
+
3
+ # This class is a special array to hold a bit of extra information about a response like:
4
+ # <ResponseMetadata>
5
+ # <RequestId>4f1fae46-bf3d-11de-a88b-7b5b3d23b3a7</RequestId>
6
+ # </ResponseMetadata>
7
+ #
8
+ # Which can be accessed directly from the array using array.response_metadata
9
+ #
10
+ class AwsResponseArray < Array
11
+
12
+ attr_accessor :response_metadata
13
+
14
+ def initialize(response_metadata)
15
+ @response_metadata = response_metadata
16
+ end
17
+
18
+ end
19
+
20
+ # Used when pulling out a single response object
21
+ class AwsResponseObjectHash < Hash
22
+
23
+ attr_accessor :response_metadata
24
+
25
+ def initialize(response_metadata)
26
+ @response_metadata = response_metadata
27
+ end
28
+
29
+ end
30
+ end
@@ -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,1239 @@
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 Aws
26
+ require 'digest/md5'
27
+ require 'pp'
28
+ require 'cgi'
29
+ require 'uri'
30
+ require 'xmlsimple'
31
+ require 'active_support/core_ext'
32
+
33
+ class AwsUtils #:nodoc:
34
+ @@digest1 = OpenSSL::Digest::Digest.new("sha1")
35
+ @@digest256 = nil
36
+ if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
37
+ @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
38
+ end
39
+
40
+ def self.sign(aws_secret_access_key, auth_string)
41
+ Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
42
+ end
43
+
44
+
45
+ # Set a timestamp and a signature version
46
+ def self.fix_service_params(service_hash, signature)
47
+ service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
48
+ service_hash["SignatureVersion"] = signature
49
+ service_hash
50
+ end
51
+
52
+ # Signature Version 0
53
+ # A deprecated guy (should work till septemper 2009)
54
+ def self.sign_request_v0(aws_secret_access_key, service_hash)
55
+ fix_service_params(service_hash, '0')
56
+ string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
57
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
58
+ service_hash.to_a.collect{|key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
59
+ end
60
+
61
+ # Signature Version 1
62
+ # Another deprecated guy (should work till septemper 2009)
63
+ def self.sign_request_v1(aws_secret_access_key, service_hash)
64
+ fix_service_params(service_hash, '1')
65
+ string_to_sign = service_hash.sort{|a, b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
66
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
67
+ service_hash.to_a.collect{|key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
68
+ end
69
+
70
+ # Signature Version 2
71
+ # EC2, SQS and SDB requests must be signed by this guy.
72
+ # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
73
+ # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
74
+ def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
75
+ fix_service_params(service_hash, '2')
76
+ # select a signing method (make an old openssl working with sha1)
77
+ # make 'HmacSHA256' to be a default one
78
+ service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
79
+ service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
80
+ # select a digest
81
+ digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
82
+ # form string to sign
83
+ canonical_string = service_hash.keys.sort.map do |key|
84
+ "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
85
+ end.join('&')
86
+ string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
87
+ # sign the string
88
+ signature = escape_sig(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
89
+ ret = "#{canonical_string}&Signature=#{signature}"
90
+ # puts 'full=' + ret.inspect
91
+ ret
92
+ end
93
+
94
+ HEX = [
95
+ "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
96
+ "%08", "%09", "%0A", "%0B", "%0C", "%0D", "%0E", "%0F",
97
+ "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
98
+ "%18", "%19", "%1A", "%1B", "%1C", "%1D", "%1E", "%1F",
99
+ "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
100
+ "%28", "%29", "%2A", "%2B", "%2C", "%2D", "%2E", "%2F",
101
+ "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
102
+ "%38", "%39", "%3A", "%3B", "%3C", "%3D", "%3E", "%3F",
103
+ "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
104
+ "%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F",
105
+ "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
106
+ "%58", "%59", "%5A", "%5B", "%5C", "%5D", "%5E", "%5F",
107
+ "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
108
+ "%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F",
109
+ "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
110
+ "%78", "%79", "%7A", "%7B", "%7C", "%7D", "%7E", "%7F",
111
+ "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
112
+ "%88", "%89", "%8A", "%8B", "%8C", "%8D", "%8E", "%8F",
113
+ "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
114
+ "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
115
+ "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7",
116
+ "%A8", "%A9", "%AA", "%AB", "%AC", "%AD", "%AE", "%AF",
117
+ "%B0", "%B1", "%B2", "%B3", "%B4", "%B5", "%B6", "%B7",
118
+ "%B8", "%B9", "%BA", "%BB", "%BC", "%BD", "%BE", "%BF",
119
+ "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
120
+ "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF",
121
+ "%D0", "%D1", "%D2", "%D3", "%D4", "%D5", "%D6", "%D7",
122
+ "%D8", "%D9", "%DA", "%DB", "%DC", "%DD", "%DE", "%DF",
123
+ "%E0", "%E1", "%E2", "%E3", "%E4", "%E5", "%E6", "%E7",
124
+ "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
125
+ "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7",
126
+ "%F8", "%F9", "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
127
+ ]
128
+ TO_REMEMBER = 'AZaz09 -_.!~*\'()'
129
+ ASCII = {} # {'A'=>65, 'Z'=>90, 'a'=>97, 'z'=>122, '0'=>48, '9'=>57, ' '=>32, '-'=>45, '_'=>95, '.'=>}
130
+ TO_REMEMBER.each_char do |c| #unpack("c*").each do |c|
131
+ ASCII[c] = c.unpack("c")[0]
132
+ end
133
+ # puts 'ascii=' + ASCII.inspect
134
+
135
+ # Escape a string accordingly Amazon rulles
136
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
137
+ def self.amz_escape(param)
138
+
139
+ param = param.to_s
140
+ # param = param.force_encoding("UTF-8")
141
+
142
+ e = "x" # escape2(param.to_s)
143
+ # puts 'ESCAPED=' + e.inspect
144
+
145
+
146
+ #return CGI.escape(param.to_s).gsub("%7E", "~").gsub("+", "%20") # from: http://umlaut.rubyforge.org/svn/trunk/lib/aws_product_sign.rb
147
+
148
+ #param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
149
+ # '%' + $1.unpack('H2' * $1.size).join('%').upcase
150
+ #end
151
+
152
+ # puts 'e in=' + e.inspect
153
+ # converter = Iconv.new('ASCII', 'UTF-8')
154
+ # e = converter.iconv(e) #.unpack('U*').select{ |cp| cp < 127 }.pack('U*')
155
+ # puts 'e out=' + e.inspect
156
+
157
+ e2 = CGI.escape(param)
158
+ e2 = e2.gsub("%7E", "~")
159
+ e2 = e2.gsub("+", "%20")
160
+ e2 = e2.gsub("*", "%2A")
161
+
162
+ # puts 'E2=' + e2.inspect
163
+ # puts e == e2.to_s
164
+
165
+ e2
166
+
167
+ end
168
+
169
+ def self.escape2(s)
170
+ # home grown
171
+ ret = ""
172
+ s.unpack("U*") do |ch|
173
+ # puts 'ch=' + ch.inspect
174
+ if ASCII['A'] <= ch && ch <= ASCII['Z'] # A to Z
175
+ ret << ch
176
+ elsif ASCII['a'] <= ch && ch <= ASCII['z'] # a to z
177
+ ret << ch
178
+ elsif ASCII['0'] <= ch && ch <= ASCII['9'] # 0 to 9
179
+ ret << ch
180
+ elsif ch == ASCII[' '] # space
181
+ ret << "%20" # "+"
182
+ elsif ch == ASCII['-'] || ch == ASCII['_'] || ch == ASCII['.'] || ch == ASCII['~']
183
+ ret << ch
184
+ elsif ch <= 0x007f # other ascii
185
+ ret << HEX[ch]
186
+ elsif ch <= 0x07FF # non-ascii
187
+ ret << HEX[0xc0 | (ch >> 6)]
188
+ ret << HEX[0x80 | (ch & 0x3F)]
189
+ else
190
+ ret << HEX[0xe0 | (ch >> 12)]
191
+ ret << HEX[0x80 | ((ch >> 6) & 0x3F)]
192
+ ret << HEX[0x80 | (ch & 0x3F)]
193
+ end
194
+
195
+ end
196
+ ret
197
+
198
+ end
199
+
200
+ def self.escape_sig(raw)
201
+ e = CGI.escape(raw)
202
+ end
203
+
204
+ # From Amazon's SQS Dev Guide, a brief description of how to escape:
205
+ # "URL encode the computed signature and other query parameters as specified in
206
+ # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space
207
+ # by Sun Java classes that perform URL decoding, make sure to encode the + character
208
+ # although it is not required by RFC1738."
209
+ # Avoid using CGI::escape to escape URIs.
210
+ # CGI::escape will escape characters in the protocol, host, and port
211
+ # sections of the URI. Only target chars in the query
212
+ # string should be escaped.
213
+ def self.URLencode(raw)
214
+ e = URI.escape(raw)
215
+ e.gsub(/\+/, "%2b")
216
+ end
217
+
218
+
219
+ def self.allow_only(allowed_keys, params)
220
+ bogus_args = []
221
+ params.keys.each {|p| bogus_args.push(p) unless allowed_keys.include?(p) }
222
+ 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
223
+ end
224
+
225
+ def self.mandatory_arguments(required_args, params)
226
+ rargs = required_args.dup
227
+ params.keys.each {|p| rargs.delete(p)}
228
+ raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
229
+ end
230
+
231
+ def self.caller_method
232
+ caller[1]=~/`(.*?)'/
233
+ $1
234
+ end
235
+
236
+ end
237
+
238
+ class AwsBenchmarkingBlock #:nodoc:
239
+ attr_accessor :xml, :service
240
+
241
+ def initialize
242
+ # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
243
+ @service = Benchmark::Tms.new()
244
+ # Benchmark::Tms instance for XML parsing benchmarking.
245
+ @xml = Benchmark::Tms.new()
246
+ end
247
+ end
248
+
249
+ class AwsNoChange < RuntimeError
250
+ end
251
+
252
+ class AwsBase
253
+
254
+ # Amazon HTTP Error handling
255
+
256
+ # Text, if found in an error message returned by AWS, indicates that this may be a transient
257
+ # error. Transient errors are automatically retried with exponential back-off.
258
+ AMAZON_PROBLEMS = [ 'internal service error',
259
+ 'is currently unavailable',
260
+ 'no response from',
261
+ 'Please try again',
262
+ 'InternalError',
263
+ 'ServiceUnavailable', #from SQS docs
264
+ 'Unavailable',
265
+ 'This application is not currently available',
266
+ 'InsufficientInstanceCapacity'
267
+ ]
268
+ @@amazon_problems = AMAZON_PROBLEMS
269
+ # Returns a list of Amazon service responses which are known to be transient problems.
270
+ # We have to re-request if we get any of them, because the problem will probably disappear.
271
+ # By default this method returns the same value as the AMAZON_PROBLEMS const.
272
+ def self.amazon_problems
273
+ @@amazon_problems
274
+ end
275
+
276
+ # Sets the list of Amazon side problems. Use in conjunction with the
277
+ # getter to append problems.
278
+ def self.amazon_problems=(problems_list)
279
+ @@amazon_problems = problems_list
280
+ end
281
+
282
+ end
283
+
284
+ module AwsBaseInterface
285
+ DEFAULT_SIGNATURE_VERSION = '2'
286
+
287
+ @@caching = false
288
+
289
+ def self.caching
290
+ @@caching
291
+ end
292
+
293
+ def self.caching=(caching)
294
+ @@caching = caching
295
+ end
296
+
297
+ # Current aws_access_key_id
298
+ attr_reader :aws_access_key_id
299
+ # Last HTTP request object
300
+ attr_reader :last_request
301
+ # Last HTTP response object
302
+ attr_reader :last_response
303
+ # Last AWS errors list (used by AWSErrorHandler)
304
+ attr_accessor :last_errors
305
+ # Last AWS request id (used by AWSErrorHandler)
306
+ attr_accessor :last_request_id
307
+ # Logger object
308
+ attr_accessor :logger
309
+ # Initial params hash
310
+ attr_accessor :params
311
+ # RightHttpConnection instance
312
+ attr_reader :connection
313
+ # Cache
314
+ attr_reader :cache
315
+ # Signature version (all services except s3)
316
+ attr_reader :signature_version
317
+
318
+ def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
319
+ @params = params
320
+ raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
321
+ if aws_access_key_id.blank? || aws_secret_access_key.blank?
322
+ @aws_access_key_id = aws_access_key_id
323
+ @aws_secret_access_key = aws_secret_access_key
324
+ # if the endpoint was explicitly defined - then use it
325
+ if @params[:endpoint_url]
326
+ @params[:server] = URI.parse(@params[:endpoint_url]).host
327
+ @params[:port] = URI.parse(@params[:endpoint_url]).port
328
+ @params[:service] = URI.parse(@params[:endpoint_url]).path
329
+ @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
330
+ @params[:region] = nil
331
+ else
332
+ @params[:server] ||= service_info[:default_host]
333
+ @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
334
+ @params[:port] ||= service_info[:default_port]
335
+ @params[:service] ||= service_info[:default_service]
336
+ @params[:protocol] ||= service_info[:default_protocol]
337
+ @params[:api_version] ||= service_info[:api_version]
338
+ end
339
+ if !@params[:multi_thread].nil? && @params[:connection_mode].nil? # user defined this
340
+ @params[:connection_mode] = @params[:multi_thread] ? :per_thread : :single
341
+ end
342
+ # @params[:multi_thread] ||= defined?(AWS_DAEMON)
343
+ @params[:connection_mode] ||= :default
344
+ @params[:connection_mode] = :per_request if @params[:connection_mode] == :default
345
+ @logger = @params[:logger]
346
+ @logger = Rails.logger if !@logger && defined?(Rails) && defined?(Rails.logger)
347
+ @logger = ::Rails.logger if !@logger && defined?(::Rails.logger)
348
+ @logger = Logger.new(STDOUT) if !@logger
349
+ @logger.info "New #{self.class.name} using #{@params[:connection_mode].to_s}-connection mode"
350
+ @error_handler = nil
351
+ @cache = {}
352
+ @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
353
+ end
354
+
355
+ def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil )
356
+ case signature_version.to_s
357
+ when '0' then
358
+ AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
359
+ when '1' then
360
+ AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
361
+ when '2' then
362
+ AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
363
+ else
364
+ raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
365
+ end
366
+ end
367
+
368
+
369
+ def generate_request(action, params={})
370
+ generate_request2(@aws_access_key_id, @aws_secret_access_key, action, @params[:api_version], @params, params)
371
+ end
372
+
373
+ # FROM SDB
374
+ def generate_request2(aws_access_key, aws_secret_key, action, api_version, lib_params, user_params={}) #:nodoc:
375
+ # remove empty params from request
376
+ user_params.delete_if {|key, value| value.nil? }
377
+ # user_params.each_pair do |k,v|
378
+ # user_params[k] = v.force_encoding("UTF-8")
379
+ # end
380
+ #params_string = params.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&")
381
+ # prepare service data
382
+ service = lib_params[:service]
383
+ # puts 'service=' + service.to_s
384
+ service_hash = {"Action" => action,
385
+ "AWSAccessKeyId" => aws_access_key }
386
+ service_hash.update("Version" => api_version) if api_version
387
+ service_hash.update(user_params)
388
+ service_params = signed_service_params(aws_secret_key, service_hash, :get, lib_params[:server], lib_params[:service])
389
+ #
390
+ # use POST method if the length of the query string is too large
391
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
392
+ if service_params.size > 2000
393
+ if signature_version == '2'
394
+ # resign the request because HTTP verb is included into signature
395
+ service_params = signed_service_params(aws_secret_key, service_hash, :post, lib_params[:server], service)
396
+ end
397
+ request = Net::HTTP::Post.new(service)
398
+ request.body = service_params
399
+ request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
400
+ else
401
+ request = Net::HTTP::Get.new("#{service}?#{service_params}")
402
+ end
403
+
404
+ #puts "\n\n --------------- QUERY REQUEST TO AWS -------------- \n\n"
405
+ #puts "#{@params[:service]}?#{service_params}\n\n"
406
+
407
+ # prepare output hash
408
+ { :request => request,
409
+ :server => lib_params[:server],
410
+ :port => lib_params[:port],
411
+ :protocol => lib_params[:protocol] }
412
+ end
413
+
414
+ def get_conn(connection_name, lib_params, logger)
415
+ # thread = lib_params[:multi_thread] ? Thread.current : Thread.main
416
+ # thread[connection_name] ||= Rightscale::HttpConnection.new(:exception => Aws::AwsError, :logger => logger)
417
+ # conn = thread[connection_name]
418
+ # return conn
419
+ http_conn = nil
420
+ conn_mode = lib_params[:connection_mode]
421
+ if conn_mode == :per_request
422
+ http_conn = Rightscale::HttpConnection.new(:exception => AwsError, :logger => logger)
423
+
424
+ elsif conn_mode == :per_thread || conn_mode == :single
425
+ thread = conn_mode == :per_thread ? Thread.current : Thread.main
426
+ thread[connection_name] ||= Rightscale::HttpConnection.new(:exception => AwsError, :logger => logger)
427
+ http_conn = thread[connection_name]
428
+ # ret = request_info_impl(http_conn, bench, request, parser, &block)
429
+ end
430
+ return http_conn
431
+
432
+ end
433
+
434
+ def close_conn(conn_name)
435
+ conn_mode = @params[:connection_mode]
436
+ if conn_mode == :per_thread || conn_mode == :single
437
+ thread = conn_mode == :per_thread ? Thread.current : Thread.main
438
+ if !thread[conn_name].nil?
439
+ thread[conn_name].finish
440
+ thread[conn_name] = nil
441
+ end
442
+ end
443
+ end
444
+
445
+ #
446
+ # def request_info2(request, parser, lib_params, connection_name, logger, bench)
447
+ # t = get_conn(connection_name, lib_params, logger)
448
+ # request_info_impl(t, bench, request, parser)
449
+ # end
450
+
451
+ # Sends request to Amazon and parses the response
452
+ # Raises AwsError if any banana happened
453
+ def request_info2(request, parser, lib_params, connection_name, logger, bench, &block) #:nodoc:
454
+ ret = nil
455
+ http_conn = get_conn(connection_name, lib_params, logger)
456
+ begin
457
+ retry_count = 1
458
+ count = 0
459
+ while count <= retry_count
460
+ puts 'RETRYING QUERY due to QueryTimeout...' if count > 0
461
+ begin
462
+ ret = request_info_impl(http_conn, bench, request, parser, &block)
463
+ break
464
+ rescue Aws::AwsError => ex
465
+ if !ex.include?(/QueryTimeout/) || count == retry_count
466
+ raise ex
467
+ end
468
+ end
469
+ count += 1
470
+ end
471
+ ensure
472
+ http_conn.finish if http_conn && lib_params[:connection_mode] == :per_request
473
+ end
474
+ ret
475
+ end
476
+
477
+
478
+ # This is the direction we should head instead of writing our own parsers for everything, much simpler
479
+ # params:
480
+ # - :group_tags => hash of indirection to eliminate, see: http://xml-simple.rubyforge.org/
481
+ # - :force_array => true for all or an array of tag names to force
482
+ # - :pull_out_array => an array of levels to dig into when generating return value (see rds.rb for example)
483
+ def request_info_xml_simple(connection_name, lib_params, request, logger, params = {})
484
+
485
+ @connection = get_conn(connection_name, lib_params, logger)
486
+ begin
487
+ @last_request = request[:request]
488
+ @last_response = nil
489
+
490
+ response = @connection.request(request)
491
+ # puts "response=" + response.body
492
+ # benchblock.service.add!{ response = @connection.request(request) }
493
+ # check response for errors...
494
+ @last_response = response
495
+ if response.is_a?(Net::HTTPSuccess)
496
+ @error_handler = nil
497
+ # benchblock.xml.add! { parser.parse(response) }
498
+ # return parser.result
499
+ force_array = params[:force_array] || false
500
+ # Force_array and group_tags don't work nice together so going to force array manually
501
+ xml_simple_options = {"KeyToSymbol"=>false, 'ForceArray' => false}
502
+ xml_simple_options["GroupTags"] = params[:group_tags] if params[:group_tags]
503
+
504
+ # { 'GroupTags' => { 'searchpath' => 'dir' }
505
+ # 'ForceArray' => %r(_list$)
506
+ parsed = XmlSimple.xml_in(response.body, xml_simple_options)
507
+ # todo: we may want to consider stripping off a couple of layers when doing this, for instance:
508
+ # <DescribeDBInstancesResponse xmlns="http://rds.amazonaws.com/admin/2009-10-16/">
509
+ # <DescribeDBInstancesResult>
510
+ # <DBInstances>
511
+ # <DBInstance>....
512
+ # Strip it off and only return an array or hash of <DBInstance>'s (hash by identifier).
513
+ # would have to be able to make the RequestId available somehow though, perhaps some special array subclass which included that?
514
+ unless force_array.is_a? Array
515
+ force_array = []
516
+ end
517
+ parsed = symbolize(parsed, force_array)
518
+ # puts 'parsed=' + parsed.inspect
519
+ if params[:pull_out_array]
520
+ ret = Aws::AwsResponseArray.new(parsed[:response_metadata])
521
+ level_hash = parsed
522
+ params[:pull_out_array].each do |x|
523
+ level_hash = level_hash[x]
524
+ end
525
+ if level_hash.is_a? Hash # When there's only one
526
+ ret << level_hash
527
+ else # should be array
528
+ # puts 'level_hash=' + level_hash.inspect
529
+ level_hash.each do |x|
530
+ ret << x
531
+ end
532
+ end
533
+ elsif params[:pull_out_single]
534
+ # returns a single object
535
+ ret = AwsResponseObjectHash.new(parsed[:response_metadata])
536
+ level_hash = parsed
537
+ params[:pull_out_single].each do |x|
538
+ level_hash = level_hash[x]
539
+ end
540
+ ret.merge!(level_hash)
541
+ else
542
+ ret = parsed
543
+ end
544
+ return ret
545
+
546
+ else
547
+ @error_handler = AWSErrorHandler.new(self, nil, :errors_list => self.class.amazon_problems) unless @error_handler
548
+ check_result = @error_handler.check(request)
549
+ if check_result
550
+ @error_handler = nil
551
+ return check_result
552
+ end
553
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
554
+ raise AwsError2.new(@last_response.code, @last_request_id, request_text_data, @last_response.body)
555
+ end
556
+ ensure
557
+ @connection.finish if @connection && lib_params[:connection_mode] == :per_request
558
+ end
559
+
560
+ end
561
+
562
+ def symbolize(hash, force_array)
563
+ ret = {}
564
+ hash.keys.each do |key|
565
+ val = hash[key]
566
+ if val.is_a? Hash
567
+ val = symbolize(val, force_array)
568
+ if force_array.include? key
569
+ val = [val]
570
+ end
571
+ elsif val.is_a? Array
572
+ val = val.collect { |x| symbolize(x, force_array) }
573
+ end
574
+ ret[key.underscore.to_sym] = val
575
+ end
576
+ ret
577
+ end
578
+
579
+ # Returns +true+ if the describe_xxx responses are being cached
580
+ def caching?
581
+ @params.key?(:cache) ? @params[:cache] : @@caching
582
+ end
583
+
584
+ # Check if the aws function response hits the cache or not.
585
+ # If the cache hits:
586
+ # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+.
587
+ # - returnes parsed response from the cache if it exists or +true+ otherwise.
588
+ # If the cache miss or the caching is off then returns +false+.
589
+ def cache_hits?(function, response, do_raise=:raise)
590
+ result = false
591
+ if caching?
592
+ function = function.to_sym
593
+ # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
594
+ response = response.sub(%r{<requestId>.+?</requestId>}, '')
595
+ response_md5 =Digest::MD5.hexdigest(response).to_s
596
+ # check for changes
597
+ unless @cache[function] && @cache[function][:response_md5] == response_md5
598
+ # well, the response is new, reset cache data
599
+ update_cache(function, {:response_md5 => response_md5,
600
+ :timestamp => Time.now,
601
+ :hits => 0,
602
+ :parsed => nil})
603
+ else
604
+ # aha, cache hits, update the data and throw an exception if needed
605
+ @cache[function][:hits] += 1
606
+ if do_raise == :raise
607
+ raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+
608
+ "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+
609
+ "hits: #{@cache[function][:hits]}.")
610
+ else
611
+ result = @cache[function][:parsed] || true
612
+ end
613
+ end
614
+ end
615
+ result
616
+ end
617
+
618
+ def update_cache(function, hash)
619
+ (@cache[function.to_sym] ||= {}).merge!(hash) if caching?
620
+ end
621
+
622
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
623
+ raise if $!.is_a?(AwsNoChange)
624
+ AwsError::on_aws_exception(self, options)
625
+ end
626
+
627
+ # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
628
+ def multi_thread
629
+ @params[:multi_thread]
630
+ end
631
+
632
+
633
+ def request_info_impl(connection, benchblock, request, parser, &block) #:nodoc:
634
+ @connection = connection
635
+ @last_request = request[:request]
636
+ @last_response = nil
637
+ response=nil
638
+ blockexception = nil
639
+
640
+ if (block != nil)
641
+ # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
642
+ # an exception may get thrown in the block body (which is high-level
643
+ # code either here or in the application) but gets caught in the
644
+ # low-level code of HttpConnection. The solution is not to let any
645
+ # exception escape the block that we pass to HttpConnection::request.
646
+ # Exceptions can originate from code directly in the block, or from user
647
+ # code called in the other block which is passed to response.read_body.
648
+ benchblock.service.add! do
649
+ responsehdr = @connection.request(request) do |response|
650
+ #########
651
+ begin
652
+ @last_response = response
653
+ if response.is_a?(Net::HTTPSuccess)
654
+ @error_handler = nil
655
+ response.read_body(&block)
656
+ else
657
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
658
+ check_result = @error_handler.check(request)
659
+ if check_result
660
+ @error_handler = nil
661
+ return check_result
662
+ end
663
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
664
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
665
+ end
666
+ rescue Exception => e
667
+ blockexception = e
668
+ end
669
+ end
670
+ #########
671
+
672
+ #OK, now we are out of the block passed to the lower level
673
+ if (blockexception)
674
+ raise blockexception
675
+ end
676
+ benchblock.xml.add! do
677
+ parser.parse(responsehdr)
678
+ end
679
+ return parser.result
680
+ end
681
+ else
682
+ benchblock.service.add!{ response = @connection.request(request) }
683
+ # check response for errors...
684
+ @last_response = response
685
+ if response.is_a?(Net::HTTPSuccess)
686
+ @error_handler = nil
687
+ benchblock.xml.add! { parser.parse(response) }
688
+ return parser.result
689
+ else
690
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
691
+ check_result = @error_handler.check(request)
692
+ if check_result
693
+ @error_handler = nil
694
+ return check_result
695
+ end
696
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
697
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
698
+ end
699
+ end
700
+ rescue
701
+ @error_handler = nil
702
+ raise
703
+ end
704
+
705
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
706
+ # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
707
+ # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
708
+ # If the caching is enabled and hit then throw AwsNoChange.
709
+ # P.S. caching works for the whole images list only! (when the list param is blank)
710
+ # check cache
711
+ response, params = request_info(link, RightDummyParser.new)
712
+ cache_hits?(method.to_sym, response.body) if use_cache
713
+ parser = parser_class.new(:logger => @logger)
714
+ benchblock.xml.add!{ parser.parse(response, params) }
715
+ result = block_given? ? yield(parser) : parser.result
716
+ # update parsed data
717
+ update_cache(method.to_sym, :parsed => result) if use_cache
718
+ result
719
+ end
720
+
721
+ # Returns Amazons request ID for the latest request
722
+ def last_request_id
723
+ @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}] && $1
724
+ end
725
+
726
+ def hash_params(prefix, list) #:nodoc:
727
+ groups = {}
728
+ list.each_index{|i| groups.update("#{prefix}.#{i+1}"=>list[i])} if list
729
+ return groups
730
+ end
731
+
732
+ end
733
+
734
+
735
+ # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
736
+ # web services raise this type of error.
737
+ # Attribute inherited by RuntimeError:
738
+ # message - the text of the error, generally as returned by AWS in its XML response.
739
+ class AwsError < RuntimeError
740
+
741
+ # either an array of errors where each item is itself an array of [code, message]),
742
+ # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
743
+ attr_reader :errors
744
+
745
+ # Request id (if exists)
746
+ attr_reader :request_id
747
+
748
+ # Response HTTP error code
749
+ attr_reader :http_code
750
+
751
+ # Raw request text data to AWS
752
+ attr_reader :request_data
753
+
754
+ attr_reader :response
755
+
756
+ def initialize(errors=nil, http_code=nil, request_id=nil, request_data=nil, response=nil)
757
+ @errors = errors
758
+ @request_id = request_id
759
+ @http_code = http_code
760
+ @request_data = request_data
761
+ @response = response
762
+ msg = @errors.is_a?(Array) ? @errors.map{|code, msg| "#{code}: #{msg}"}.join("; ") : @errors.to_s
763
+ msg += "\nREQUEST=#{@request_data} " unless @request_data.nil?
764
+ msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil?
765
+ super(msg)
766
+ end
767
+
768
+ # Does any of the error messages include the regexp +pattern+?
769
+ # Used to determine whether to retry request.
770
+ def include?(pattern)
771
+ if @errors.is_a?(Array)
772
+ @errors.each{ |code, msg| return true if code =~ pattern }
773
+ else
774
+ return true if @errors_str =~ pattern
775
+ end
776
+ false
777
+ end
778
+
779
+ # Generic handler for AwsErrors. +aws+ is the Aws::S3, Aws::EC2, or Aws::SQS
780
+ # object that caused the exception (it must provide last_request and last_response). Supported
781
+ # boolean options are:
782
+ # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
783
+ # * <tt>:puts</tt> do a "puts" of the error
784
+ # * <tt>:raise</tt> re-raise the error after logging
785
+ def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
786
+ # Only log & notify if not user error
787
+ if !options[:raise] || system_error?($!)
788
+ error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
789
+ puts error_text if options[:puts]
790
+ # Log the error
791
+ if options[:log]
792
+ request = aws.last_request ? aws.last_request.path : '-none-'
793
+ response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
794
+ @response = response
795
+ aws.logger.error error_text
796
+ aws.logger.error "Request was: #{request}"
797
+ aws.logger.error "Response was: #{response}"
798
+ end
799
+ end
800
+ raise if options[:raise] # re-raise an exception
801
+ return nil
802
+ end
803
+
804
+ # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
805
+ # Used to force logging.
806
+ def self.system_error?(e)
807
+ !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
808
+ end
809
+
810
+ end
811
+
812
+ # Simplified version
813
+ class AwsError2 < RuntimeError
814
+ # Request id (if exists)
815
+ attr_reader :request_id
816
+
817
+ # Response HTTP error code
818
+ attr_reader :http_code
819
+
820
+ # Raw request text data to AWS
821
+ attr_reader :request_data
822
+
823
+ attr_reader :response
824
+
825
+ attr_reader :errors
826
+
827
+ def initialize(http_code=nil, request_id=nil, request_data=nil, response=nil)
828
+
829
+ @request_id = request_id
830
+ @http_code = http_code
831
+ @request_data = request_data
832
+ @response = response
833
+ # puts '@response=' + @response.inspect
834
+
835
+ if @response
836
+ ref = XmlSimple.xml_in(@response, { "ForceArray"=>false })
837
+ # puts "refxml=" + ref.inspect
838
+ msg = "#{ref['Error']['Code']}: #{ref['Error']['Message']}"
839
+ else
840
+ msg = "#{@http_code}: REQUEST(#{@request_data})"
841
+ end
842
+ msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil?
843
+ super(msg)
844
+ end
845
+
846
+
847
+ end
848
+
849
+
850
+ class AWSErrorHandler
851
+ # 0-100 (%)
852
+ DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
853
+
854
+ @@reiteration_start_delay = 0.2
855
+
856
+ def self.reiteration_start_delay
857
+ @@reiteration_start_delay
858
+ end
859
+
860
+ def self.reiteration_start_delay=(reiteration_start_delay)
861
+ @@reiteration_start_delay = reiteration_start_delay
862
+ end
863
+
864
+ @@reiteration_time = 5
865
+
866
+ def self.reiteration_time
867
+ @@reiteration_time
868
+ end
869
+
870
+ def self.reiteration_time=(reiteration_time)
871
+ @@reiteration_time = reiteration_time
872
+ end
873
+
874
+ @@close_on_error = true
875
+
876
+ def self.close_on_error
877
+ @@close_on_error
878
+ end
879
+
880
+ def self.close_on_error=(close_on_error)
881
+ @@close_on_error = close_on_error
882
+ end
883
+
884
+ @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
885
+
886
+ def self.close_on_4xx_probability
887
+ @@close_on_4xx_probability
888
+ end
889
+
890
+ def self.close_on_4xx_probability=(close_on_4xx_probability)
891
+ @@close_on_4xx_probability = close_on_4xx_probability
892
+ end
893
+
894
+ # params:
895
+ # :reiteration_time
896
+ # :errors_list
897
+ # :close_on_error = true | false
898
+ # :close_on_4xx_probability = 1-100
899
+ def initialize(aws, parser, params={}) #:nodoc:
900
+ @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
901
+ @parser = parser # parser to parse Amazon response
902
+ @started_at = Time.now
903
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
904
+ @errors_list = params[:errors_list] || []
905
+ @reiteration_delay = @@reiteration_start_delay
906
+ @retries = 0
907
+ # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
908
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
909
+ @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
910
+ end
911
+
912
+ # Returns false if
913
+ def check(request) #:nodoc:
914
+ result = false
915
+ error_found = false
916
+ redirect_detected= false
917
+ error_match = nil
918
+ last_errors_text = ''
919
+ response = @aws.last_response
920
+ # log error
921
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
922
+ # is this a redirect?
923
+ # yes!
924
+ if response.is_a?(Net::HTTPRedirection)
925
+ redirect_detected = true
926
+ else
927
+ # no, it's an error ...
928
+ @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
929
+ @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
930
+ end
931
+ # Check response body: if it is an Amazon XML document or not:
932
+ if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
933
+ @aws.class.bench_xml.add! do
934
+ error_parser = RightErrorResponseParser.new
935
+ error_parser.parse(response)
936
+ @aws.last_errors = error_parser.errors
937
+ @aws.last_request_id = error_parser.requestID
938
+ last_errors_text = @aws.last_errors.flatten.join("\n")
939
+ # on redirect :
940
+ if redirect_detected
941
+ location = response['location']
942
+ # ... log information and ...
943
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
944
+ @aws.logger.info("##### New location: #{location} #####")
945
+ # ... fix the connection data
946
+ request[:server] = URI.parse(location).host
947
+ request[:protocol] = URI.parse(location).scheme
948
+ request[:port] = URI.parse(location).port
949
+ end
950
+ end
951
+ else # ... it is not a xml document(probably just a html page?)
952
+ @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
953
+ @aws.last_request_id = '-undefined-'
954
+ last_errors_text = response.message
955
+ end
956
+ # now - check the error
957
+ unless redirect_detected
958
+ @errors_list.each do |error_to_find|
959
+ if last_errors_text[/#{error_to_find}/i]
960
+ error_found = true
961
+ error_match = error_to_find
962
+ @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
963
+ break
964
+ end
965
+ end
966
+ end
967
+ # check the time has gone from the first error come
968
+ if redirect_detected || error_found
969
+ # Close the connection to the server and recreate a new one.
970
+ # It may have a chance that one server is a semi-down and reconnection
971
+ # will help us to connect to the other server
972
+ if !redirect_detected && @close_on_error
973
+ @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
974
+ end
975
+
976
+ if (Time.now < @stop_at)
977
+ @retries += 1
978
+ unless redirect_detected
979
+ @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
980
+ sleep @reiteration_delay
981
+ @reiteration_delay *= 2
982
+
983
+ # Always make sure that the fp is set to point to the beginning(?)
984
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
985
+ if (request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
986
+ begin
987
+ request[:request].body_stream.pos = 0
988
+ rescue Exception => e
989
+ @logger.warn("Retry may fail due to unable to reset the file pointer" +
990
+ " -- #{self.class.name} : #{e.inspect}")
991
+ end
992
+ end
993
+ else
994
+ @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
995
+ end
996
+ result = @aws.request_info(request, @parser)
997
+ else
998
+ @aws.logger.warn("##### Ooops, time is over... ####")
999
+ end
1000
+ # aha, this is unhandled error:
1001
+ elsif @close_on_error
1002
+ # Is this a 5xx error ?
1003
+ if @aws.last_response.code.to_s[/^5\d\d$/]
1004
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
1005
+ # Is this a 4xx error ?
1006
+ elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
1007
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
1008
+ "probability: #{@close_on_4xx_probability}%"
1009
+ end
1010
+ end
1011
+ result
1012
+ end
1013
+
1014
+ end
1015
+
1016
+
1017
+ #-----------------------------------------------------------------
1018
+
1019
+ class RightSaxParserCallback #:nodoc:
1020
+ def self.include_callback
1021
+ include XML::SaxParser::Callbacks
1022
+ end
1023
+
1024
+ def initialize(right_aws_parser)
1025
+ @right_aws_parser = right_aws_parser
1026
+ end
1027
+
1028
+ def on_start_element(name, attr_hash)
1029
+ @right_aws_parser.tag_start(name, attr_hash)
1030
+ end
1031
+
1032
+ def on_characters(chars)
1033
+ @right_aws_parser.text(chars)
1034
+ end
1035
+
1036
+ def on_end_element(name)
1037
+ @right_aws_parser.tag_end(name)
1038
+ end
1039
+
1040
+ def on_start_document;
1041
+ end
1042
+
1043
+ def on_comment(msg)
1044
+ ;
1045
+ end
1046
+
1047
+ def on_processing_instruction(target, data)
1048
+ ;
1049
+ end
1050
+
1051
+ def on_cdata_block(cdata)
1052
+ ;
1053
+ end
1054
+
1055
+ def on_end_document;
1056
+ end
1057
+ end
1058
+
1059
+ class AwsParser #:nodoc:
1060
+ # default parsing library
1061
+ DEFAULT_XML_LIBRARY = 'rexml'
1062
+ # a list of supported parsers
1063
+ @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml']
1064
+
1065
+ @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml'
1066
+ def self.xml_lib
1067
+ @@xml_lib
1068
+ end
1069
+
1070
+ def self.xml_lib=(new_lib_name)
1071
+ @@xml_lib = new_lib_name
1072
+ end
1073
+
1074
+ attr_accessor :result
1075
+ attr_reader :xmlpath
1076
+ attr_accessor :xml_lib
1077
+
1078
+ def initialize(params={})
1079
+ @xmlpath = ''
1080
+ @result = false
1081
+ @text = ''
1082
+ @xml_lib = params[:xml_lib] || @@xml_lib
1083
+ @logger = params[:logger]
1084
+ reset
1085
+ end
1086
+
1087
+ def tag_start(name, attributes)
1088
+ @text = ''
1089
+ tagstart(name, attributes)
1090
+ @xmlpath += @xmlpath.empty? ? name : "/#{name}"
1091
+ end
1092
+
1093
+ def tag_end(name)
1094
+ if @xmlpath =~ /^(.*?)\/?#{name}$/
1095
+ @xmlpath = $1
1096
+ end
1097
+ tagend(name)
1098
+ end
1099
+
1100
+ def text(text)
1101
+ @text += text
1102
+ tagtext(text)
1103
+ end
1104
+
1105
+ # Parser method.
1106
+ # Params:
1107
+ # xml_text - xml message text(String) or Net:HTTPxxx instance (response)
1108
+ # params[:xml_lib] - library name: 'rexml' | 'libxml'
1109
+ def parse(xml_text, params={})
1110
+ # Get response body
1111
+ unless xml_text.is_a?(String)
1112
+ xml_text = xml_text.body.respond_to?(:force_encoding) ? xml_text.body.force_encoding("UTF-8") : xml_text.body
1113
+ end
1114
+
1115
+ @xml_lib = params[:xml_lib] || @xml_lib
1116
+ # check that we had no problems with this library otherwise use default
1117
+ @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib)
1118
+ # load xml library
1119
+ if @xml_lib=='libxml' && !defined?(XML::SaxParser)
1120
+ begin
1121
+ require 'xml/libxml'
1122
+ # is it new ? - Setup SaxParserCallback
1123
+ if XML::Parser::VERSION >= '0.5.1.0'
1124
+ RightSaxParserCallback.include_callback
1125
+ end
1126
+ rescue LoadError => e
1127
+ @@supported_xml_libs.delete(@xml_lib)
1128
+ @xml_lib = DEFAULT_XML_LIBRARY
1129
+ if @logger
1130
+ @logger.error e.inspect
1131
+ @logger.error e.backtrace
1132
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
1133
+ end
1134
+ end
1135
+ end
1136
+ # Parse the xml text
1137
+ case @xml_lib
1138
+ when 'libxml'
1139
+ xml = XML::SaxParser.string(xml_text)
1140
+ # check libxml-ruby version
1141
+ if XML::Parser::VERSION >= '0.5.1.0'
1142
+ xml.callbacks = RightSaxParserCallback.new(self)
1143
+ else
1144
+ xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
1145
+ xml.on_characters{ |text| self.text(text)}
1146
+ xml.on_end_element{ |name| self.tag_end(name)}
1147
+ end
1148
+ xml.parse
1149
+ else
1150
+ REXML::Document.parse_stream(xml_text, self)
1151
+ end
1152
+ end
1153
+
1154
+ # Parser must have a lots of methods
1155
+ # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb)
1156
+ # We dont need most of them in AwsParser and method_missing helps us
1157
+ # to skip their definition
1158
+ def method_missing(method, *params)
1159
+ # if the method is one of known - just skip it ...
1160
+ return if [:comment, :attlistdecl, :notationdecl, :elementdecl,
1161
+ :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction,
1162
+ :doctype].include?(method)
1163
+ # ... else - call super to raise an exception
1164
+ super(method, params)
1165
+ end
1166
+
1167
+ # the functions to be overriden by children (if nessesery)
1168
+ def reset;
1169
+ end
1170
+
1171
+ def tagstart(name, attributes)
1172
+ ;
1173
+ end
1174
+
1175
+ def tagend(name)
1176
+ ;
1177
+ end
1178
+
1179
+ def tagtext(text)
1180
+ ;
1181
+ end
1182
+ end
1183
+
1184
+ #-----------------------------------------------------------------
1185
+ # PARSERS: Errors
1186
+ #-----------------------------------------------------------------
1187
+
1188
+ #<Error>
1189
+ # <Code>TemporaryRedirect</Code>
1190
+ # <Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message>
1191
+ # <RequestId>FD8D5026D1C5ABA3</RequestId>
1192
+ # <Endpoint>bucket-for-k.s3-external-3.amazonaws.com</Endpoint>
1193
+ # <HostId>ItJy8xPFPli1fq/JR3DzQd3iDvFCRqi1LTRmunEdM1Uf6ZtW2r2kfGPWhRE1vtaU</HostId>
1194
+ # <Bucket>bucket-for-k</Bucket>
1195
+ #</Error>
1196
+
1197
+ class RightErrorResponseParser < AwsParser #:nodoc:
1198
+ attr_accessor :errors # array of hashes: error/message
1199
+ attr_accessor :requestID
1200
+ # attr_accessor :endpoint, :host_id, :bucket
1201
+ def tagend(name)
1202
+ case name
1203
+ when 'RequestID';
1204
+ @requestID = @text
1205
+ when 'Code';
1206
+ @code = @text
1207
+ when 'Message';
1208
+ @message = @text
1209
+ # when 'Endpoint' ; @endpoint = @text
1210
+ # when 'HostId' ; @host_id = @text
1211
+ # when 'Bucket' ; @bucket = @text
1212
+ when 'Error';
1213
+ @errors << [ @code, @message ]
1214
+ end
1215
+ end
1216
+
1217
+ def reset
1218
+ @errors = []
1219
+ end
1220
+ end
1221
+
1222
+ # Dummy parser - does nothing
1223
+ # Returns the original params back
1224
+ class RightDummyParser # :nodoc:
1225
+ attr_accessor :result
1226
+
1227
+ def parse(response, params={})
1228
+ @result = [response, params]
1229
+ end
1230
+ end
1231
+
1232
+ class RightHttp2xxParser < AwsParser # :nodoc:
1233
+ def parse(response)
1234
+ @result = response.is_a?(Net::HTTPSuccess)
1235
+ end
1236
+ end
1237
+
1238
+ end
1239
+