gotime_aws 2.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,296 @@
1
+ module Aws
2
+
3
+ class ResourceNotFoundError < StandardError
4
+
5
+ end
6
+
7
+ # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
8
+ # web services raise this type of error.
9
+ # Attribute inherited by RuntimeError:
10
+ # message - the text of the error, generally as returned by AWS in its XML response.
11
+ class AwsError < RuntimeError
12
+
13
+ # either an array of errors where each item is itself an array of [code, message]),
14
+ # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
15
+ attr_reader :errors
16
+
17
+ # Request id (if exists)
18
+ attr_reader :request_id
19
+
20
+ # Response HTTP error code
21
+ attr_reader :http_code
22
+
23
+ # Raw request text data to AWS
24
+ attr_reader :request_data
25
+
26
+ attr_reader :response
27
+
28
+ def initialize(errors=nil, http_code=nil, request_id=nil, request_data=nil, response=nil)
29
+ @errors = errors
30
+ @request_id = request_id
31
+ @http_code = http_code
32
+ @request_data = request_data
33
+ @response = response
34
+ msg = @errors.is_a?(Array) ? @errors.map { |code, m| "#{code}: #{m}" }.join("; ") : @errors.to_s
35
+ msg += "\nREQUEST=#{@request_data} " unless @request_data.nil?
36
+ msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil?
37
+ super(msg)
38
+ end
39
+
40
+ # Does any of the error messages include the regexp +pattern+?
41
+ # Used to determine whether to retry request.
42
+ def include?(pattern_or_string)
43
+ if pattern_or_string.is_a?(String)
44
+ puts 'convert to pattern'
45
+ pattern_or_string = /#{pattern_or_string}/
46
+ end
47
+ if @errors.is_a?(Array)
48
+ @errors.each { |code, msg| return true if code =~ pattern_or_string }
49
+ else
50
+ return true if @errors_str =~ pattern_or_string
51
+ end
52
+ false
53
+ end
54
+
55
+ # Generic handler for AwsErrors. +aws+ is the Aws::S3, Aws::EC2, or Aws::SQS
56
+ # object that caused the exception (it must provide last_request and last_response). Supported
57
+ # boolean options are:
58
+ # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
59
+ # * <tt>:puts</tt> do a "puts" of the error
60
+ # * <tt>:raise</tt> re-raise the error after logging
61
+ def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
62
+ # Only log & notify if not user error
63
+ if !options[:raise] || system_error?($!)
64
+ error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
65
+ puts error_text if options[:puts]
66
+ # Log the error
67
+ if options[:log]
68
+ request = aws.last_request ? aws.last_request.path : '-none-'
69
+ response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
70
+ @response = response
71
+ aws.logger.error error_text
72
+ aws.logger.error "Request was: #{request}"
73
+ aws.logger.error "Response was: #{response}"
74
+ end
75
+ end
76
+ raise if options[:raise] # re-raise an exception
77
+ return nil
78
+ end
79
+
80
+ # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
81
+ # Used to force logging.
82
+ def self.system_error?(e)
83
+ !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
84
+ end
85
+
86
+ end
87
+
88
+ # Simplified version
89
+ class AwsError2 < RuntimeError
90
+ # Request id (if exists)
91
+ attr_reader :request_id
92
+
93
+ # Response HTTP error code
94
+ attr_reader :http_code
95
+
96
+ # Raw request text data to AWS
97
+ attr_reader :request_data
98
+
99
+ attr_reader :response
100
+
101
+ attr_reader :errors
102
+
103
+ def initialize(http_code=nil, request_id=nil, request_data=nil, response=nil)
104
+
105
+ @request_id = request_id
106
+ @http_code = http_code
107
+ @request_data = request_data
108
+ @response = response
109
+ # puts '@response=' + @response.inspect
110
+
111
+ if @response
112
+ ref = XmlSimple.xml_in(@response, {"ForceArray"=>false})
113
+ # puts "refxml=" + ref.inspect
114
+ msg = "#{ref['Error']['Code']}: #{ref['Error']['Message']}"
115
+ else
116
+ msg = "#{@http_code}: REQUEST(#{@request_data})"
117
+ end
118
+ msg += "\nREQUEST ID=#{@request_id} " unless @request_id.nil?
119
+ super(msg)
120
+ end
121
+
122
+
123
+ end
124
+
125
+
126
+ class AWSErrorHandler
127
+ # 0-100 (%)
128
+ DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
129
+
130
+ @@reiteration_start_delay = 0.2
131
+
132
+ def self.reiteration_start_delay
133
+ @@reiteration_start_delay
134
+ end
135
+
136
+ def self.reiteration_start_delay=(reiteration_start_delay)
137
+ @@reiteration_start_delay = reiteration_start_delay
138
+ end
139
+
140
+ @@reiteration_time = 5
141
+
142
+ def self.reiteration_time
143
+ @@reiteration_time
144
+ end
145
+
146
+ def self.reiteration_time=(reiteration_time)
147
+ @@reiteration_time = reiteration_time
148
+ end
149
+
150
+ @@close_on_error = true
151
+
152
+ def self.close_on_error
153
+ @@close_on_error
154
+ end
155
+
156
+ def self.close_on_error=(close_on_error)
157
+ @@close_on_error = close_on_error
158
+ end
159
+
160
+ @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
161
+
162
+ def self.close_on_4xx_probability
163
+ @@close_on_4xx_probability
164
+ end
165
+
166
+ def self.close_on_4xx_probability=(close_on_4xx_probability)
167
+ @@close_on_4xx_probability = close_on_4xx_probability
168
+ end
169
+
170
+ # params:
171
+ # :reiteration_time
172
+ # :errors_list
173
+ # :close_on_error = true | false
174
+ # :close_on_4xx_probability = 1-100
175
+ def initialize(aws, parser, params={}) #:nodoc:
176
+ @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
177
+ @parser = parser # parser to parse Amazon response
178
+ @started_at = Time.now
179
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
180
+ @errors_list = params[:errors_list] || []
181
+ @reiteration_delay = @@reiteration_start_delay
182
+ @retries = 0
183
+ # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
184
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
185
+ @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
186
+ end
187
+
188
+ # Returns false if
189
+ def check(request, options={}) #:nodoc:
190
+ result = false
191
+ error_found = false
192
+ redirect_detected = false
193
+ error_match = nil
194
+ last_errors_text = ''
195
+ response = @aws.last_response
196
+ # log error
197
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
198
+ # is this a redirect?
199
+ # yes!
200
+ if response.is_a?(Net::HTTPRedirection)
201
+ redirect_detected = true
202
+ else
203
+ # no, it's an error ...
204
+ @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
205
+ @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
206
+ end
207
+ # Check response body: if it is an Amazon XML document or not:
208
+ if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
209
+ @aws.class.bench_xml.add! do
210
+ error_parser = RightErrorResponseParser.new
211
+ error_parser.parse(response)
212
+ @aws.last_errors = error_parser.errors
213
+ @aws.last_request_id = error_parser.requestID
214
+ last_errors_text = @aws.last_errors.flatten.join("\n")
215
+ # on redirect :
216
+ if redirect_detected
217
+ location = response['location']
218
+ # ... log information and ...
219
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
220
+ @aws.logger.info("##### New location: #{location} #####")
221
+ # ... fix the connection data
222
+ request[:server] = URI.parse(location).host
223
+ request[:protocol] = URI.parse(location).scheme
224
+ request[:port] = URI.parse(location).port
225
+ end
226
+ end
227
+ else # ... it is not a xml document(probably just a html page?)
228
+ @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
229
+ @aws.last_request_id = '-undefined-'
230
+ last_errors_text = response.message
231
+ end
232
+ # now - check the error
233
+ unless redirect_detected
234
+ @errors_list.each do |error_to_find|
235
+ if last_errors_text[/#{error_to_find}/i]
236
+ error_found = true
237
+ error_match = error_to_find
238
+ @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
239
+ break
240
+ end
241
+ end
242
+ end
243
+ # check the time has gone from the first error come
244
+ if redirect_detected || error_found
245
+ # Close the connection to the server and recreate a new one.
246
+ # It may have a chance that one server is a semi-down and reconnection
247
+ # will help us to connect to the other server
248
+ if !redirect_detected && @close_on_error
249
+ @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
250
+ end
251
+ # puts 'OPTIONS3=' + options.inspect
252
+ if options[:retries].nil? || @retries < options[:retries]
253
+ if (Time.now < @stop_at)
254
+ @retries += 1
255
+ unless redirect_detected
256
+ @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
257
+ sleep @reiteration_delay
258
+ @reiteration_delay *= 2
259
+
260
+ # Always make sure that the fp is set to point to the beginning(?)
261
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
262
+ if (request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
263
+ begin
264
+ request[:request].body_stream.pos = 0
265
+ rescue Exception => e
266
+ @logger.warn("Retry may fail due to unable to reset the file pointer" +
267
+ " -- #{self.class.name} : #{e.inspect}")
268
+ end
269
+ end
270
+ else
271
+ @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
272
+ end
273
+ result = @aws.request_info(request, @parser, options)
274
+ else
275
+ @aws.logger.warn("##### Ooops, time is over... ####")
276
+ end
277
+ else
278
+ @aws.logger.info("##### Stopped retrying because retries=#{@retries} and max=#{options[:retries]} ####")
279
+ end
280
+ # aha, this is unhandled error:
281
+ elsif @close_on_error
282
+ # Is this a 5xx error ?
283
+ if @aws.last_response.code.to_s[/^5\d\d$/]
284
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
285
+ # Is this a 4xx error ?
286
+ elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
287
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
288
+ "probability: #{@close_on_4xx_probability}%"
289
+ end
290
+ end
291
+ result
292
+ end
293
+
294
+ end
295
+
296
+ 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
@@ -0,0 +1,20 @@
1
+ # require_relative was introduced in 1.9.2. This makes it
2
+ # available to younger rubies. It trys hard to not re-require
3
+ # files.
4
+
5
+ unless Kernel.respond_to?(:require_relative)
6
+ module Kernel
7
+ def require_relative(path)
8
+ desired_path = File.expand_path('../' + path.to_str, caller[0])
9
+ shortest = desired_path
10
+ $:.each do |path|
11
+ path += '/'
12
+ if desired_path.index(path) == 0
13
+ candidate = desired_path.sub(path, '')
14
+ shortest = candidate if candidate.size < shortest.size
15
+ end
16
+ end
17
+ require shortest
18
+ end
19
+ end
20
+ end