aws 2.3.34 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,10 +24,10 @@
24
24
 
25
25
 
26
26
  # A hack because there's a bug in add! in Benchmark::Tms
27
- module Benchmark #:nodoc:
27
+ module Benchmark #:nodoc:
28
28
  class Tms #:nodoc:
29
29
  def add!(&blk)
30
- t = Benchmark::measure(&blk)
30
+ t = Benchmark::measure(&blk)
31
31
  @utime = utime + t.utime
32
32
  @stime = stime + t.stime
33
33
  @cutime = cutime + t.cutime
@@ -0,0 +1,288 @@
1
+ module Aws
2
+
3
+ # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
4
+ # web services raise this type of error.
5
+ # Attribute inherited by RuntimeError:
6
+ # message - the text of the error, generally as returned by AWS in its XML response.
7
+ class AwsError < RuntimeError
8
+
9
+ # either an array of errors where each item is itself an array of [code, message]),
10
+ # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
11
+ attr_reader :errors
12
+
13
+ # Request id (if exists)
14
+ attr_reader :request_id
15
+
16
+ # Response HTTP error code
17
+ attr_reader :http_code
18
+
19
+ # Raw request text data to AWS
20
+ attr_reader :request_data
21
+
22
+ attr_reader :response
23
+
24
+ def initialize(errors=nil, http_code=nil, request_id=nil, request_data=nil, response=nil)
25
+ @errors = errors
26
+ @request_id = request_id
27
+ @http_code = http_code
28
+ @request_data = request_data
29
+ @response = response
30
+ msg = @errors.is_a?(Array) ? @errors.map { |code, msg| "#{code}: #{msg}" }.join("; ") : @errors.to_s
31
+ msg += "\nREQUEST=#{@request_data} " unless @request_data.nil?
32
+ msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil?
33
+ super(msg)
34
+ end
35
+
36
+ # Does any of the error messages include the regexp +pattern+?
37
+ # Used to determine whether to retry request.
38
+ def include?(pattern)
39
+ if @errors.is_a?(Array)
40
+ @errors.each { |code, msg| return true if code =~ pattern }
41
+ else
42
+ return true if @errors_str =~ pattern
43
+ end
44
+ false
45
+ end
46
+
47
+ # Generic handler for AwsErrors. +aws+ is the Aws::S3, Aws::EC2, or Aws::SQS
48
+ # object that caused the exception (it must provide last_request and last_response). Supported
49
+ # boolean options are:
50
+ # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
51
+ # * <tt>:puts</tt> do a "puts" of the error
52
+ # * <tt>:raise</tt> re-raise the error after logging
53
+ def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
54
+ # Only log & notify if not user error
55
+ if !options[:raise] || system_error?($!)
56
+ error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
57
+ puts error_text if options[:puts]
58
+ # Log the error
59
+ if options[:log]
60
+ request = aws.last_request ? aws.last_request.path : '-none-'
61
+ response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
62
+ @response = response
63
+ aws.logger.error error_text
64
+ aws.logger.error "Request was: #{request}"
65
+ aws.logger.error "Response was: #{response}"
66
+ end
67
+ end
68
+ raise if options[:raise] # re-raise an exception
69
+ return nil
70
+ end
71
+
72
+ # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
73
+ # Used to force logging.
74
+ def self.system_error?(e)
75
+ !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
76
+ end
77
+
78
+ end
79
+
80
+ # Simplified version
81
+ class AwsError2 < RuntimeError
82
+ # Request id (if exists)
83
+ attr_reader :request_id
84
+
85
+ # Response HTTP error code
86
+ attr_reader :http_code
87
+
88
+ # Raw request text data to AWS
89
+ attr_reader :request_data
90
+
91
+ attr_reader :response
92
+
93
+ attr_reader :errors
94
+
95
+ def initialize(http_code=nil, request_id=nil, request_data=nil, response=nil)
96
+
97
+ @request_id = request_id
98
+ @http_code = http_code
99
+ @request_data = request_data
100
+ @response = response
101
+ # puts '@response=' + @response.inspect
102
+
103
+ if @response
104
+ ref = XmlSimple.xml_in(@response, {"ForceArray"=>false})
105
+ # puts "refxml=" + ref.inspect
106
+ msg = "#{ref['Error']['Code']}: #{ref['Error']['Message']}"
107
+ else
108
+ msg = "#{@http_code}: REQUEST(#{@request_data})"
109
+ end
110
+ msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil?
111
+ super(msg)
112
+ end
113
+
114
+
115
+ end
116
+
117
+
118
+ class AWSErrorHandler
119
+ # 0-100 (%)
120
+ DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
121
+
122
+ @@reiteration_start_delay = 0.2
123
+
124
+ def self.reiteration_start_delay
125
+ @@reiteration_start_delay
126
+ end
127
+
128
+ def self.reiteration_start_delay=(reiteration_start_delay)
129
+ @@reiteration_start_delay = reiteration_start_delay
130
+ end
131
+
132
+ @@reiteration_time = 5
133
+
134
+ def self.reiteration_time
135
+ @@reiteration_time
136
+ end
137
+
138
+ def self.reiteration_time=(reiteration_time)
139
+ @@reiteration_time = reiteration_time
140
+ end
141
+
142
+ @@close_on_error = true
143
+
144
+ def self.close_on_error
145
+ @@close_on_error
146
+ end
147
+
148
+ def self.close_on_error=(close_on_error)
149
+ @@close_on_error = close_on_error
150
+ end
151
+
152
+ @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
153
+
154
+ def self.close_on_4xx_probability
155
+ @@close_on_4xx_probability
156
+ end
157
+
158
+ def self.close_on_4xx_probability=(close_on_4xx_probability)
159
+ @@close_on_4xx_probability = close_on_4xx_probability
160
+ end
161
+
162
+ # params:
163
+ # :reiteration_time
164
+ # :errors_list
165
+ # :close_on_error = true | false
166
+ # :close_on_4xx_probability = 1-100
167
+ def initialize(aws, parser, params={}) #:nodoc:
168
+ @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
169
+ @parser = parser # parser to parse Amazon response
170
+ @started_at = Time.now
171
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
172
+ @errors_list = params[:errors_list] || []
173
+ @reiteration_delay = @@reiteration_start_delay
174
+ @retries = 0
175
+ # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
176
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
177
+ @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
178
+ end
179
+
180
+ # Returns false if
181
+ def check(request, options={}) #:nodoc:
182
+ result = false
183
+ error_found = false
184
+ redirect_detected = false
185
+ error_match = nil
186
+ last_errors_text = ''
187
+ response = @aws.last_response
188
+ # log error
189
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
190
+ # is this a redirect?
191
+ # yes!
192
+ if response.is_a?(Net::HTTPRedirection)
193
+ redirect_detected = true
194
+ else
195
+ # no, it's an error ...
196
+ @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
197
+ @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
198
+ end
199
+ # Check response body: if it is an Amazon XML document or not:
200
+ if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
201
+ @aws.class.bench_xml.add! do
202
+ error_parser = RightErrorResponseParser.new
203
+ error_parser.parse(response)
204
+ @aws.last_errors = error_parser.errors
205
+ @aws.last_request_id = error_parser.requestID
206
+ last_errors_text = @aws.last_errors.flatten.join("\n")
207
+ # on redirect :
208
+ if redirect_detected
209
+ location = response['location']
210
+ # ... log information and ...
211
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
212
+ @aws.logger.info("##### New location: #{location} #####")
213
+ # ... fix the connection data
214
+ request[:server] = URI.parse(location).host
215
+ request[:protocol] = URI.parse(location).scheme
216
+ request[:port] = URI.parse(location).port
217
+ end
218
+ end
219
+ else # ... it is not a xml document(probably just a html page?)
220
+ @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
221
+ @aws.last_request_id = '-undefined-'
222
+ last_errors_text = response.message
223
+ end
224
+ # now - check the error
225
+ unless redirect_detected
226
+ @errors_list.each do |error_to_find|
227
+ if last_errors_text[/#{error_to_find}/i]
228
+ error_found = true
229
+ error_match = error_to_find
230
+ @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
231
+ break
232
+ end
233
+ end
234
+ end
235
+ # check the time has gone from the first error come
236
+ if redirect_detected || error_found
237
+ # Close the connection to the server and recreate a new one.
238
+ # It may have a chance that one server is a semi-down and reconnection
239
+ # will help us to connect to the other server
240
+ if !redirect_detected && @close_on_error
241
+ @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
242
+ end
243
+ # puts 'OPTIONS3=' + options.inspect
244
+ if options[:retries].nil? || @retries < options[:retries]
245
+ if (Time.now < @stop_at)
246
+ @retries += 1
247
+ unless redirect_detected
248
+ @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
249
+ sleep @reiteration_delay
250
+ @reiteration_delay *= 2
251
+
252
+ # Always make sure that the fp is set to point to the beginning(?)
253
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
254
+ if (request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
255
+ begin
256
+ request[:request].body_stream.pos = 0
257
+ rescue Exception => e
258
+ @logger.warn("Retry may fail due to unable to reset the file pointer" +
259
+ " -- #{self.class.name} : #{e.inspect}")
260
+ end
261
+ end
262
+ else
263
+ @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
264
+ end
265
+ result = @aws.request_info(request, @parser, options)
266
+ else
267
+ @aws.logger.warn("##### Ooops, time is over... ####")
268
+ end
269
+ else
270
+ @aws.logger.info("##### Stopped retrying because retries=#{@retries} and max=#{options[:retries]} ####")
271
+ end
272
+ # aha, this is unhandled error:
273
+ elsif @close_on_error
274
+ # Is this a 5xx error ?
275
+ if @aws.last_response.code.to_s[/^5\d\d$/]
276
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
277
+ # Is this a 4xx error ?
278
+ elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
279
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
280
+ "probability: #{@close_on_4xx_probability}%"
281
+ end
282
+ end
283
+ result
284
+ end
285
+
286
+ end
287
+
288
+ end
@@ -0,0 +1,227 @@
1
+ module Aws
2
+
3
+ #-----------------------------------------------------------------
4
+
5
+ class RightSaxParserCallback #:nodoc:
6
+ def self.include_callback
7
+ include XML::SaxParser::Callbacks
8
+ end
9
+
10
+ def initialize(right_aws_parser)
11
+ @right_aws_parser = right_aws_parser
12
+ end
13
+
14
+ def on_start_element(name, attr_hash)
15
+ @right_aws_parser.tag_start(name, attr_hash)
16
+ end
17
+
18
+ def on_characters(chars)
19
+ @right_aws_parser.text(chars)
20
+ end
21
+
22
+ def on_end_element(name)
23
+ @right_aws_parser.tag_end(name)
24
+ end
25
+
26
+ def on_start_document;
27
+ end
28
+
29
+ def on_comment(msg)
30
+ ;
31
+ end
32
+
33
+ def on_processing_instruction(target, data)
34
+ ;
35
+ end
36
+
37
+ def on_cdata_block(cdata)
38
+ ;
39
+ end
40
+
41
+ def on_end_document;
42
+ end
43
+ end
44
+
45
+ class AwsParser #:nodoc:
46
+ # default parsing library
47
+ DEFAULT_XML_LIBRARY = 'rexml'
48
+ # a list of supported parsers
49
+ @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml']
50
+
51
+ @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml'
52
+ def self.xml_lib
53
+ @@xml_lib
54
+ end
55
+
56
+ def self.xml_lib=(new_lib_name)
57
+ @@xml_lib = new_lib_name
58
+ end
59
+
60
+ attr_accessor :result
61
+ attr_reader :xmlpath
62
+ attr_accessor :xml_lib
63
+
64
+ def initialize(params={})
65
+ @xmlpath = ''
66
+ @result = false
67
+ @text = ''
68
+ @xml_lib = params[:xml_lib] || @@xml_lib
69
+ @logger = params[:logger]
70
+ reset
71
+ end
72
+
73
+ def tag_start(name, attributes)
74
+ @text = ''
75
+ tagstart(name, attributes)
76
+ @xmlpath += @xmlpath.empty? ? name : "/#{name}"
77
+ end
78
+
79
+ def tag_end(name)
80
+ if @xmlpath =~ /^(.*?)\/?#{name}$/
81
+ @xmlpath = $1
82
+ end
83
+ tagend(name)
84
+ end
85
+
86
+ def text(text)
87
+ @text += text
88
+ tagtext(text)
89
+ end
90
+
91
+ # Parser method.
92
+ # Params:
93
+ # xml_text - xml message text(String) or Net:HTTPxxx instance (response)
94
+ # params[:xml_lib] - library name: 'rexml' | 'libxml'
95
+ def parse(xml_text, params={})
96
+ # Get response body
97
+ unless xml_text.is_a?(String)
98
+ xml_text = xml_text.body.respond_to?(:force_encoding) ? xml_text.body.force_encoding("UTF-8") : xml_text.body
99
+ end
100
+
101
+ @xml_lib = params[:xml_lib] || @xml_lib
102
+ # check that we had no problems with this library otherwise use default
103
+ @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib)
104
+ # load xml library
105
+ if @xml_lib=='libxml' && !defined?(XML::SaxParser)
106
+ begin
107
+ require 'xml/libxml'
108
+ # is it new ? - Setup SaxParserCallback
109
+ if XML::Parser::VERSION >= '0.5.1.0'
110
+ RightSaxParserCallback.include_callback
111
+ end
112
+ rescue LoadError => e
113
+ @@supported_xml_libs.delete(@xml_lib)
114
+ @xml_lib = DEFAULT_XML_LIBRARY
115
+ if @logger
116
+ @logger.error e.inspect
117
+ @logger.error e.backtrace
118
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
119
+ end
120
+ end
121
+ end
122
+ # Parse the xml text
123
+ case @xml_lib
124
+ when 'libxml'
125
+ xml = XML::SaxParser.string(xml_text)
126
+ # check libxml-ruby version
127
+ if XML::Parser::VERSION >= '0.5.1.0'
128
+ xml.callbacks = RightSaxParserCallback.new(self)
129
+ else
130
+ xml.on_start_element { |name, attr_hash| self.tag_start(name, attr_hash) }
131
+ xml.on_characters { |text| self.text(text) }
132
+ xml.on_end_element { |name| self.tag_end(name) }
133
+ end
134
+ xml.parse
135
+ else
136
+ REXML::Document.parse_stream(xml_text, self)
137
+ end
138
+ end
139
+
140
+ # Parser must have a lots of methods
141
+ # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb)
142
+ # We dont need most of them in AwsParser and method_missing helps us
143
+ # to skip their definition
144
+ def method_missing(method, *params)
145
+ # if the method is one of known - just skip it ...
146
+ return if [:comment, :attlistdecl, :notationdecl, :elementdecl,
147
+ :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction,
148
+ :doctype].include?(method)
149
+ # ... else - call super to raise an exception
150
+ super(method, params)
151
+ end
152
+
153
+ # the functions to be overriden by children (if nessesery)
154
+ def reset;
155
+ end
156
+
157
+ def tagstart(name, attributes)
158
+ ;
159
+ end
160
+
161
+ def tagend(name)
162
+ ;
163
+ end
164
+
165
+ def tagtext(text)
166
+ ;
167
+ end
168
+ end
169
+
170
+ #-----------------------------------------------------------------
171
+ # PARSERS: Errors
172
+ #-----------------------------------------------------------------
173
+
174
+ #<Error>
175
+ # <Code>TemporaryRedirect</Code>
176
+ # <Message>Please re-send this request to the specified temporary endpoint. Continue to use the original request endpoint for future requests.</Message>
177
+ # <RequestId>FD8D5026D1C5ABA3</RequestId>
178
+ # <Endpoint>bucket-for-k.s3-external-3.amazonaws.com</Endpoint>
179
+ # <HostId>ItJy8xPFPli1fq/JR3DzQd3iDvFCRqi1LTRmunEdM1Uf6ZtW2r2kfGPWhRE1vtaU</HostId>
180
+ # <Bucket>bucket-for-k</Bucket>
181
+ #</Error>
182
+
183
+ class RightErrorResponseParser < AwsParser #:nodoc:
184
+ attr_accessor :errors # array of hashes: error/message
185
+ attr_accessor :requestID
186
+ # attr_accessor :endpoint, :host_id, :bucket
187
+ def parse(response)
188
+ super
189
+ end
190
+
191
+ def tagend(name)
192
+ case name
193
+ when 'RequestID';
194
+ @requestID = @text
195
+ when 'Code';
196
+ @code = @text
197
+ when 'Message';
198
+ @message = @text
199
+ # when 'Endpoint' ; @endpoint = @text
200
+ # when 'HostId' ; @host_id = @text
201
+ # when 'Bucket' ; @bucket = @text
202
+ when 'Error';
203
+ @errors << [@code, @message]
204
+ end
205
+ end
206
+
207
+ def reset
208
+ @errors = []
209
+ end
210
+ end
211
+
212
+ # Dummy parser - does nothing
213
+ # Returns the original params back
214
+ class RightDummyParser # :nodoc:
215
+ attr_accessor :result
216
+
217
+ def parse(response, params={})
218
+ @result = [response, params]
219
+ end
220
+ end
221
+
222
+ class RightHttp2xxParser < AwsParser # :nodoc:
223
+ def parse(response)
224
+ @result = response.is_a?(Net::HTTPSuccess)
225
+ end
226
+ end
227
+ end