aws 2.3.34 → 2.4.0

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