aws 1.11.9 → 1.11.36

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.
@@ -1,27 +1,51 @@
1
- = RightScale Amazon Web Services Ruby Gems
1
+ # Appoxy AWS Library
2
2
 
3
- http://code.google.com/p/simple-record/
3
+ The Appoxy AWS gem is a forked version of RightScale's AWS library.
4
4
 
5
- Published by RightScale, Inc. under the MIT License.
6
- For information about RightScale, see http://www.rightscale.com
5
+ ## Why the Fork?
7
6
 
8
- == DESCRIPTION:
7
+ 1. RightScale wasn't fixing critical bugs that were reported in their forums.
8
+ 1. It didn't work with Ruby 1.9 - this version does
9
+ 1. RightScale doesn't have the source hosted for the community
10
+ 1. We needed fixes and changes for [http://code.google.com/p/simple-record/ SimpleRecord] and didn't want to wait for RightScale to do it.
11
+ 1. We needed Elastic Load Balancing support
9
12
 
10
- The RightScale AWS gems have been designed to provide a robust, fast, and secure interface to Amazon EC2, EBS, S3, SQS, SDB, and CloudFront.
11
- These gems have been used in production by RightScale since late 2006 and are being maintained to track enhancements made by Amazon.
13
+ ## Discussion Group
14
+
15
+ http://groups.google.com/group/ruby-aws
16
+
17
+ ## Issue Tracker
18
+
19
+ http://appoxy.lighthouseapp.com/projects/38441-aws/overview
20
+
21
+ ## Appoxy Amazon Web Services Ruby Gems
22
+
23
+ Published by Appoxy LLC, under the MIT License. Special thanks to RightScale from which this project is forked.
24
+
25
+ ## INSTALL:
26
+
27
+ gem install appoxy-aws
28
+
29
+
30
+ ## DESCRIPTION:
31
+
32
+ The RightScale AWS gems have been designed to provide a robust, fast, and secure interface to Amazon EC2, EBS, S3, SQS, SDB, and CloudFront.
33
+ These gems have been used in production by RightScale since late 2006 and are being maintained to track enhancements made by Amazon.
12
34
  The RightScale AWS gems comprise:
13
35
 
14
- - RightAws::Ec2 -- interface to Amazon EC2 (Elastic Compute Cloud) and the
15
- associated EBS (Elastic Block Store)
36
+ - RightAws::Ec2 -- interface to Amazon EC2 (Elastic Compute Cloud) and the associated EBS (Elastic Block Store)
16
37
  - RightAws::S3 and RightAws::S3Interface -- interface to Amazon S3 (Simple Storage Service)
17
38
  - RightAws::Sqs and RightAws::SqsInterface -- interface to first-generation Amazon SQS (Simple Queue Service) (API version 2007-05-01)
18
39
  - RightAws::SqsGen2 and RightAws::SqsGen2Interface -- interface to second-generation Amazon SQS (Simple Queue Service) (API version 2008-01-01)
19
40
  - RightAws::SdbInterface and RightAws::ActiveSdb -- interface to Amazon SDB (SimpleDB)
20
41
  - RightAws::AcfInterface -- interface to Amazon CloudFront, a content distribution service
42
+ - RightAws::ElbInterface -- interface to Amazon Load Balancing service
43
+ - RightAws::MonInterface -- interface to Amazon CloudWatch monitoring service
44
+
21
45
 
22
- == FEATURES:
46
+ ## FEATURES:
23
47
 
24
- - Full programmmatic access to EC2, EBS, S3, SQS, SDB, and CloudFront.
48
+ - Full programmmatic access to EC2, EBS, S3, SQS, SDB, ELB, and CloudFront.
25
49
  - Complete error handling: all operations check for errors and report complete
26
50
  error information by raising an AwsError.
27
51
  - Persistent HTTP connections with robust network-level retry layer using
@@ -44,7 +68,7 @@ The RightScale AWS gems comprise:
44
68
  - Interoperability with any cloud running Eucalyptus (http://eucalyptus.cs.ucsb.edu)
45
69
  - Test suite (requires AWS account to do "live" testing).
46
70
 
47
- == THREADING:
71
+ ## THREADING:
48
72
 
49
73
  All RightScale AWS interfaces offer two threading options:
50
74
  1. Use a single persistent HTTP connection per process.
@@ -68,10 +92,10 @@ Note that due to limitations in the I/O of the Ruby interpreter you
68
92
  may not get the degree of parallelism you may expect with the multi-threaded setting.
69
93
 
70
94
  By default, EC2/S3/SQS/SDB/ACF interface instances are created in single-threaded mode. Set
71
- "params[:multi_thread]" to "true" in the initialization arguments to use
95
+ params[:connection_mode] to :multi_thread in the initialization arguments to use
72
96
  multithreaded mode.
73
97
 
74
- == GETTING STARTED:
98
+ ## GETTING STARTED:
75
99
 
76
100
  * For EC2 read RightAws::Ec2 and consult the Amazon EC2 API documentation at
77
101
  http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=87
@@ -90,7 +114,7 @@ multithreaded mode.
90
114
  * For CloudFront (ACF) read RightAws::AcfInterface and consult the Amazon CloudFront API documentation at
91
115
  http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=213
92
116
 
93
- == KNOWN ISSUES:
117
+ ## KNOWN ISSUES:
94
118
 
95
119
  - 7/08: A user has reported that uploads of large files on Windows may be broken on some
96
120
  Win platforms due to a buggy File.lstat.size. Use the following monkey-patch at your own risk,
@@ -131,7 +155,7 @@ multithreaded mode.
131
155
  RightAws::SqsInterface will fail with the message:
132
156
  "AWS.SimpleQueueService.QueueDeletedRecently: You must wait 60 seconds after deleting a queue before you can create another with the same name."
133
157
 
134
- == REQUIREMENTS:
158
+ ## REQUIREMENTS:
135
159
 
136
160
  RightAws requires REXML and the right_http_connection gem.
137
161
  If libxml and its Ruby bindings (distributed in the libxml-ruby gem) are
@@ -140,9 +164,6 @@ present, RightAws can be configured to use them:
140
164
  Any error with the libxml installation will result in RightAws failing-safe to
141
165
  REXML parsing.
142
166
 
143
- == INSTALL:
144
-
145
- sudo gem install right_aws
146
167
 
147
168
  == LICENSE:
148
169
 
@@ -78,8 +78,8 @@ module RightAws
78
78
 
79
79
  API_VERSION = "2008-06-30"
80
80
  DEFAULT_HOST = 'cloudfront.amazonaws.com'
81
- DEFAULT_PORT = 443
82
- DEFAULT_PROTOCOL = 'https'
81
+ DEFAULT_PORT = 80
82
+ DEFAULT_PROTOCOL = 'http'
83
83
  DEFAULT_PATH = '/'
84
84
 
85
85
  @@bench = AwsBenchmarkingBlock.new
@@ -23,744 +23,801 @@
23
23
 
24
24
  # Test
25
25
  module RightAws
26
- require 'digest/md5'
27
- require 'pp'
28
-
29
- class AwsUtils #:nodoc:
30
- @@digest1 = OpenSSL::Digest::Digest.new("sha1")
31
- @@digest256 = nil
32
- if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
33
- @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
34
- end
26
+ require 'digest/md5'
27
+ require 'pp'
28
+ require 'cgi'
29
+ require 'uri'
30
+
31
+ class AwsUtils #:nodoc:
32
+ @@digest1 = OpenSSL::Digest::Digest.new("sha1")
33
+ @@digest256 = nil
34
+ if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
35
+ @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
36
+ end
35
37
 
36
- def self.sign(aws_secret_access_key, auth_string)
37
- Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
38
- end
38
+ def self.sign(aws_secret_access_key, auth_string)
39
+ Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
40
+ end
39
41
 
40
- # Escape a string accordingly Amazon rulles
41
- # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
42
- def self.amz_escape(param)
43
- param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
44
- '%' + $1.unpack('H2' * $1.size).join('%').upcase
45
- end
46
- end
47
42
 
48
- # Set a timestamp and a signature version
49
- def self.fix_service_params(service_hash, signature)
50
- service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
51
- service_hash["SignatureVersion"] = signature
52
- service_hash
53
- end
43
+ # Set a timestamp and a signature version
44
+ def self.fix_service_params(service_hash, signature)
45
+ service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
46
+ service_hash["SignatureVersion"] = signature
47
+ service_hash
48
+ end
54
49
 
55
- # Signature Version 0
56
- # A deprecated guy (should work till septemper 2009)
57
- def self.sign_request_v0(aws_secret_access_key, service_hash)
58
- fix_service_params(service_hash, '0')
59
- string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
60
- service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
61
- service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
62
- end
50
+ # Signature Version 0
51
+ # A deprecated guy (should work till septemper 2009)
52
+ def self.sign_request_v0(aws_secret_access_key, service_hash)
53
+ fix_service_params(service_hash, '0')
54
+ string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
55
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
56
+ service_hash.to_a.collect{|key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
57
+ end
63
58
 
64
- # Signature Version 1
65
- # Another deprecated guy (should work till septemper 2009)
66
- def self.sign_request_v1(aws_secret_access_key, service_hash)
67
- fix_service_params(service_hash, '1')
68
- string_to_sign = service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
69
- service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
70
- service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
71
- end
59
+ # Signature Version 1
60
+ # Another deprecated guy (should work till septemper 2009)
61
+ def self.sign_request_v1(aws_secret_access_key, service_hash)
62
+ fix_service_params(service_hash, '1')
63
+ string_to_sign = service_hash.sort{|a, b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
64
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
65
+ service_hash.to_a.collect{|key, val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
66
+ end
72
67
 
73
- # Signature Version 2
74
- # EC2, SQS and SDB requests must be signed by this guy.
75
- # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
76
- # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
77
- def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
78
- fix_service_params(service_hash, '2')
79
- # select a signing method (make an old openssl working with sha1)
80
- # make 'HmacSHA256' to be a default one
81
- service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
82
- service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
83
- # select a digest
84
- digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
85
- # form string to sign
86
- canonical_string = service_hash.keys.sort.map do |key|
87
- "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
88
- end.join('&')
89
- string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
90
- # sign the string
91
- signature = amz_escape(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
92
- "#{canonical_string}&Signature=#{signature}"
93
- end
68
+ # Signature Version 2
69
+ # EC2, SQS and SDB requests must be signed by this guy.
70
+ # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
71
+ # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
72
+ def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
73
+ fix_service_params(service_hash, '2')
74
+ # select a signing method (make an old openssl working with sha1)
75
+ # make 'HmacSHA256' to be a default one
76
+ service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
77
+ service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
78
+ # select a digest
79
+ digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
80
+ # form string to sign
81
+ canonical_string = service_hash.keys.sort.map do |key|
82
+ "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
83
+ end.join('&')
84
+ string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
85
+ # sign the string
86
+ signature = escape_sig(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
87
+ "#{canonical_string}&Signature=#{signature}"
88
+ end
94
89
 
95
- # From Amazon's SQS Dev Guide, a brief description of how to escape:
96
- # "URL encode the computed signature and other query parameters as specified in
97
- # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space
98
- # by Sun Java classes that perform URL decoding, make sure to encode the + character
99
- # although it is not required by RFC1738."
100
- # Avoid using CGI::escape to escape URIs.
101
- # CGI::escape will escape characters in the protocol, host, and port
102
- # sections of the URI. Only target chars in the query
103
- # string should be escaped.
104
- def self.URLencode(raw)
105
- e = URI.escape(raw)
106
- e.gsub(/\+/, "%2b")
107
- end
90
+ # Escape a string accordingly Amazon rulles
91
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
92
+ def self.amz_escape(param)
93
+ #return CGI.escape(param.to_s).gsub("%7E", "~").gsub("+", "%20") # from: http://umlaut.rubyforge.org/svn/trunk/lib/aws_product_sign.rb
108
94
 
109
- def self.allow_only(allowed_keys, params)
110
- bogus_args = []
111
- params.keys.each {|p| bogus_args.push(p) unless allowed_keys.include?(p) }
112
- 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
113
- end
95
+ #param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
96
+ # '%' + $1.unpack('H2' * $1.size).join('%').upcase
97
+ #end
98
+ e = CGI.escape(param.to_s)
99
+ e = e.gsub("%7E", "~")
100
+ e = e.gsub("+", "%20")
101
+ e = e.gsub("*", "%2A")
114
102
 
115
- def self.mandatory_arguments(required_args, params)
116
- rargs = required_args.dup
117
- params.keys.each {|p| rargs.delete(p)}
118
- raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
119
- end
103
+ end
120
104
 
121
- def self.caller_method
122
- caller[1]=~/`(.*?)'/
123
- $1
124
- end
125
105
 
126
- end
106
+ def self.escape_sig(raw)
107
+ e = CGI.escape(raw)
108
+ end
109
+
110
+ # From Amazon's SQS Dev Guide, a brief description of how to escape:
111
+ # "URL encode the computed signature and other query parameters as specified in
112
+ # RFC1738, section 2.2. In addition, because the + character is interpreted as a blank space
113
+ # by Sun Java classes that perform URL decoding, make sure to encode the + character
114
+ # although it is not required by RFC1738."
115
+ # Avoid using CGI::escape to escape URIs.
116
+ # CGI::escape will escape characters in the protocol, host, and port
117
+ # sections of the URI. Only target chars in the query
118
+ # string should be escaped.
119
+ def self.URLencode(raw)
120
+ e = URI.escape(raw)
121
+ e.gsub(/\+/, "%2b")
122
+ end
123
+
124
+
125
+ def self.allow_only(allowed_keys, params)
126
+ bogus_args = []
127
+ params.keys.each {|p| bogus_args.push(p) unless allowed_keys.include?(p) }
128
+ 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
129
+ end
130
+
131
+ def self.mandatory_arguments(required_args, params)
132
+ rargs = required_args.dup
133
+ params.keys.each {|p| rargs.delete(p)}
134
+ raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
135
+ end
136
+
137
+ def self.caller_method
138
+ caller[1]=~/`(.*?)'/
139
+ $1
140
+ end
127
141
 
128
- class AwsBenchmarkingBlock #:nodoc:
129
- attr_accessor :xml, :service
130
- def initialize
131
- # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
132
- @service = Benchmark::Tms.new()
133
- # Benchmark::Tms instance for XML parsing benchmarking.
134
- @xml = Benchmark::Tms.new()
135
142
  end
136
- end
137
-
138
- class AwsNoChange < RuntimeError
139
- end
140
-
141
- class RightAwsBase
142
-
143
- # Amazon HTTP Error handling
144
-
145
- # Text, if found in an error message returned by AWS, indicates that this may be a transient
146
- # error. Transient errors are automatically retried with exponential back-off.
147
- AMAZON_PROBLEMS = [ 'internal service error',
148
- 'is currently unavailable',
149
- 'no response from',
150
- 'Please try again',
151
- 'InternalError',
152
- 'ServiceUnavailable', #from SQS docs
153
- 'Unavailable',
154
- 'This application is not currently available',
155
- 'InsufficientInstanceCapacity'
156
- ]
157
- @@amazon_problems = AMAZON_PROBLEMS
158
- # Returns a list of Amazon service responses which are known to be transient problems.
159
- # We have to re-request if we get any of them, because the problem will probably disappear.
160
- # By default this method returns the same value as the AMAZON_PROBLEMS const.
161
- def self.amazon_problems
162
- @@amazon_problems
143
+
144
+ class AwsBenchmarkingBlock #:nodoc:
145
+ attr_accessor :xml, :service
146
+
147
+ def initialize
148
+ # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
149
+ @service = Benchmark::Tms.new()
150
+ # Benchmark::Tms instance for XML parsing benchmarking.
151
+ @xml = Benchmark::Tms.new()
152
+ end
163
153
  end
164
154
 
165
- # Sets the list of Amazon side problems. Use in conjunction with the
166
- # getter to append problems.
167
- def self.amazon_problems=(problems_list)
168
- @@amazon_problems = problems_list
155
+ class AwsNoChange < RuntimeError
169
156
  end
170
157
 
171
- end
158
+ class RightAwsBase
172
159
 
173
- module RightAwsBaseInterface
174
- DEFAULT_SIGNATURE_VERSION = '2'
160
+ # Amazon HTTP Error handling
161
+
162
+ # Text, if found in an error message returned by AWS, indicates that this may be a transient
163
+ # error. Transient errors are automatically retried with exponential back-off.
164
+ AMAZON_PROBLEMS = [ 'internal service error',
165
+ 'is currently unavailable',
166
+ 'no response from',
167
+ 'Please try again',
168
+ 'InternalError',
169
+ 'ServiceUnavailable', #from SQS docs
170
+ 'Unavailable',
171
+ 'This application is not currently available',
172
+ 'InsufficientInstanceCapacity'
173
+ ]
174
+ @@amazon_problems = AMAZON_PROBLEMS
175
+ # Returns a list of Amazon service responses which are known to be transient problems.
176
+ # We have to re-request if we get any of them, because the problem will probably disappear.
177
+ # By default this method returns the same value as the AMAZON_PROBLEMS const.
178
+ def self.amazon_problems
179
+ @@amazon_problems
180
+ end
181
+
182
+ # Sets the list of Amazon side problems. Use in conjunction with the
183
+ # getter to append problems.
184
+ def self.amazon_problems=(problems_list)
185
+ @@amazon_problems = problems_list
186
+ end
175
187
 
176
- @@caching = false
177
- def self.caching
178
- @@caching
179
- end
180
- def self.caching=(caching)
181
- @@caching = caching
182
188
  end
183
189
 
184
- # Current aws_access_key_id
185
- attr_reader :aws_access_key_id
186
- # Last HTTP request object
187
- attr_reader :last_request
188
- # Last HTTP response object
189
- attr_reader :last_response
190
- # Last AWS errors list (used by AWSErrorHandler)
191
- attr_accessor :last_errors
192
- # Last AWS request id (used by AWSErrorHandler)
193
- attr_accessor :last_request_id
194
- # Logger object
195
- attr_accessor :logger
196
- # Initial params hash
197
- attr_accessor :params
198
- # RightHttpConnection instance
199
- attr_reader :connection
200
- # Cache
201
- attr_reader :cache
202
- # Signature version (all services except s3)
203
- attr_reader :signature_version
204
-
205
- def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
206
- @params = params
207
- raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
190
+ module RightAwsBaseInterface
191
+ DEFAULT_SIGNATURE_VERSION = '2'
192
+
193
+ @@caching = false
194
+ def self.caching
195
+ @@caching
196
+ end
197
+ def self.caching=(caching)
198
+ @@caching = caching
199
+ end
200
+
201
+ # Current aws_access_key_id
202
+ attr_reader :aws_access_key_id
203
+ # Last HTTP request object
204
+ attr_reader :last_request
205
+ # Last HTTP response object
206
+ attr_reader :last_response
207
+ # Last AWS errors list (used by AWSErrorHandler)
208
+ attr_accessor :last_errors
209
+ # Last AWS request id (used by AWSErrorHandler)
210
+ attr_accessor :last_request_id
211
+ # Logger object
212
+ attr_accessor :logger
213
+ # Initial params hash
214
+ attr_accessor :params
215
+ # RightHttpConnection instance
216
+ attr_reader :connection
217
+ # Cache
218
+ attr_reader :cache
219
+ # Signature version (all services except s3)
220
+ attr_reader :signature_version
221
+
222
+ def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
223
+ @params = params
224
+ raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
208
225
  if aws_access_key_id.blank? || aws_secret_access_key.blank?
209
- @aws_access_key_id = aws_access_key_id
210
- @aws_secret_access_key = aws_secret_access_key
211
- # if the endpoint was explicitly defined - then use it
212
- if @params[:endpoint_url]
213
- @params[:server] = URI.parse(@params[:endpoint_url]).host
214
- @params[:port] = URI.parse(@params[:endpoint_url]).port
215
- @params[:service] = URI.parse(@params[:endpoint_url]).path
216
- @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
217
- @params[:region] = nil
218
- else
219
- @params[:server] ||= service_info[:default_host]
220
- @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
221
- @params[:port] ||= service_info[:default_port]
222
- @params[:service] ||= service_info[:default_service]
223
- @params[:protocol] ||= service_info[:default_protocol]
224
- end
225
- if !@params[:multi_thread].nil? && @params[:connection_mode].nil? # user defined this
226
- @params[:connection_mode] = @params[:multi_thread] ? :per_thread : :single
227
- end
226
+ @aws_access_key_id = aws_access_key_id
227
+ @aws_secret_access_key = aws_secret_access_key
228
+ # if the endpoint was explicitly defined - then use it
229
+ if @params[:endpoint_url]
230
+ @params[:server] = URI.parse(@params[:endpoint_url]).host
231
+ @params[:port] = URI.parse(@params[:endpoint_url]).port
232
+ @params[:service] = URI.parse(@params[:endpoint_url]).path
233
+ @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
234
+ @params[:region] = nil
235
+ else
236
+ @params[:server] ||= service_info[:default_host]
237
+ @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
238
+ @params[:port] ||= service_info[:default_port]
239
+ @params[:service] ||= service_info[:default_service]
240
+ @params[:protocol] ||= service_info[:default_protocol]
241
+ end
242
+ if !@params[:multi_thread].nil? && @params[:connection_mode].nil? # user defined this
243
+ @params[:connection_mode] = @params[:multi_thread] ? :per_thread : :single
244
+ end
228
245
  # @params[:multi_thread] ||= defined?(AWS_DAEMON)
229
- @params[:connection_mode] ||= :default
230
- @params[:connection_mode] = :per_request if @params[:connection_mode] == :default
231
- @logger = @params[:logger]
232
- @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
233
- @logger = Logger.new(STDOUT) if !@logger
234
- @logger.info "New #{self.class.name} using #{@params[:connection_mode].to_s}-connection mode"
235
- @error_handler = nil
236
- @cache = {}
237
- @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
238
- end
246
+ @params[:connection_mode] ||= :default
247
+ @params[:connection_mode] = :per_request if @params[:connection_mode] == :default
248
+ @logger = @params[:logger]
249
+ @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
250
+ @logger = Logger.new(STDOUT) if !@logger
251
+ @logger.info "New #{self.class.name} using #{@params[:connection_mode].to_s}-connection mode"
252
+ @error_handler = nil
253
+ @cache = {}
254
+ @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
255
+ end
239
256
 
240
- def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil )
241
- case signature_version.to_s
242
- when '0' then AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
243
- when '1' then AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
244
- when '2' then AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
245
- else raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
246
- end
247
- end
257
+ def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil )
258
+ case signature_version.to_s
259
+ when '0' then
260
+ AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
261
+ when '1' then
262
+ AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
263
+ when '2' then
264
+ AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
265
+ else
266
+ raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
267
+ end
268
+ end
248
269
 
249
- # Returns +true+ if the describe_xxx responses are being cached
250
- def caching?
251
- @params.key?(:cache) ? @params[:cache] : @@caching
252
- end
270
+ # Returns +true+ if the describe_xxx responses are being cached
271
+ def caching?
272
+ @params.key?(:cache) ? @params[:cache] : @@caching
273
+ end
253
274
 
254
- # Check if the aws function response hits the cache or not.
255
- # If the cache hits:
256
- # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+.
257
- # - returnes parsed response from the cache if it exists or +true+ otherwise.
258
- # If the cache miss or the caching is off then returns +false+.
259
- def cache_hits?(function, response, do_raise=:raise)
260
- result = false
261
- if caching?
262
- function = function.to_sym
263
- # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
264
- response = response.sub(%r{<requestId>.+?</requestId>}, '')
265
- response_md5 =Digest::MD5.hexdigest(response).to_s
266
- # check for changes
267
- unless @cache[function] && @cache[function][:response_md5] == response_md5
268
- # well, the response is new, reset cache data
269
- update_cache(function, {:response_md5 => response_md5,
270
- :timestamp => Time.now,
271
- :hits => 0,
272
- :parsed => nil})
273
- else
274
- # aha, cache hits, update the data and throw an exception if needed
275
- @cache[function][:hits] += 1
276
- if do_raise == :raise
277
- raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+
278
- "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+
279
- "hits: #{@cache[function][:hits]}.")
280
- else
281
- result = @cache[function][:parsed] || true
282
- end
283
- end
284
- end
285
- result
286
- end
275
+ # Check if the aws function response hits the cache or not.
276
+ # If the cache hits:
277
+ # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+.
278
+ # - returnes parsed response from the cache if it exists or +true+ otherwise.
279
+ # If the cache miss or the caching is off then returns +false+.
280
+ def cache_hits?(function, response, do_raise=:raise)
281
+ result = false
282
+ if caching?
283
+ function = function.to_sym
284
+ # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
285
+ response = response.sub(%r{<requestId>.+?</requestId>}, '')
286
+ response_md5 =Digest::MD5.hexdigest(response).to_s
287
+ # check for changes
288
+ unless @cache[function] && @cache[function][:response_md5] == response_md5
289
+ # well, the response is new, reset cache data
290
+ update_cache(function, {:response_md5 => response_md5,
291
+ :timestamp => Time.now,
292
+ :hits => 0,
293
+ :parsed => nil})
294
+ else
295
+ # aha, cache hits, update the data and throw an exception if needed
296
+ @cache[function][:hits] += 1
297
+ if do_raise == :raise
298
+ raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+
299
+ "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+
300
+ "hits: #{@cache[function][:hits]}.")
301
+ else
302
+ result = @cache[function][:parsed] || true
303
+ end
304
+ end
305
+ end
306
+ result
307
+ end
287
308
 
288
- def update_cache(function, hash)
289
- (@cache[function.to_sym] ||= {}).merge!(hash) if caching?
290
- end
309
+ def update_cache(function, hash)
310
+ (@cache[function.to_sym] ||= {}).merge!(hash) if caching?
311
+ end
291
312
 
292
- def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
293
- raise if $!.is_a?(AwsNoChange)
294
- AwsError::on_aws_exception(self, options)
295
- end
313
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
314
+ raise if $!.is_a?(AwsNoChange)
315
+ AwsError::on_aws_exception(self, options)
316
+ end
296
317
 
297
- # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
298
- def multi_thread
299
- @params[:multi_thread]
300
- end
318
+ # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
319
+ def multi_thread
320
+ @params[:multi_thread]
321
+ end
301
322
 
302
- def request_info_impl(connection, benchblock, request, parser, &block) #:nodoc:
303
- @connection = connection
304
- @last_request = request[:request]
305
- @last_response = nil
306
- response=nil
307
- blockexception = nil
308
-
309
- if(block != nil)
310
- # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
311
- # an exception may get thrown in the block body (which is high-level
312
- # code either here or in the application) but gets caught in the
313
- # low-level code of HttpConnection. The solution is not to let any
314
- # exception escape the block that we pass to HttpConnection::request.
315
- # Exceptions can originate from code directly in the block, or from user
316
- # code called in the other block which is passed to response.read_body.
317
- benchblock.service.add! do
318
- responsehdr = @connection.request(request) do |response|
319
- #########
320
- begin
321
- @last_response = response
322
- if response.is_a?(Net::HTTPSuccess)
323
- @error_handler = nil
324
- response.read_body(&block)
325
- else
326
- @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
327
- check_result = @error_handler.check(request)
328
- if check_result
329
- @error_handler = nil
330
- return check_result
323
+ def request_info_impl(connection, benchblock, request, parser, &block) #:nodoc:
324
+ @connection = connection
325
+ @last_request = request[:request]
326
+ @last_response = nil
327
+ response=nil
328
+ blockexception = nil
329
+
330
+ if (block != nil)
331
+ # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
332
+ # an exception may get thrown in the block body (which is high-level
333
+ # code either here or in the application) but gets caught in the
334
+ # low-level code of HttpConnection. The solution is not to let any
335
+ # exception escape the block that we pass to HttpConnection::request.
336
+ # Exceptions can originate from code directly in the block, or from user
337
+ # code called in the other block which is passed to response.read_body.
338
+ benchblock.service.add! do
339
+ responsehdr = @connection.request(request) do |response|
340
+ #########
341
+ begin
342
+ @last_response = response
343
+ if response.is_a?(Net::HTTPSuccess)
344
+ @error_handler = nil
345
+ response.read_body(&block)
346
+ else
347
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
348
+ check_result = @error_handler.check(request)
349
+ if check_result
350
+ @error_handler = nil
351
+ return check_result
352
+ end
353
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
354
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
355
+ end
356
+ rescue Exception => e
357
+ blockexception = e
358
+ end
359
+ end
360
+ #########
361
+
362
+ #OK, now we are out of the block passed to the lower level
363
+ if (blockexception)
364
+ raise blockexception
365
+ end
366
+ benchblock.xml.add! do
367
+ parser.parse(responsehdr)
368
+ end
369
+ return parser.result
370
+ end
371
+ else
372
+ benchblock.service.add!{ response = @connection.request(request) }
373
+ # check response for errors...
374
+ @last_response = response
375
+ if response.is_a?(Net::HTTPSuccess)
376
+ @error_handler = nil
377
+ benchblock.xml.add! { parser.parse(response) }
378
+ return parser.result
379
+ else
380
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
381
+ check_result = @error_handler.check(request)
382
+ if check_result
383
+ @error_handler = nil
384
+ return check_result
385
+ end
386
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
387
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
331
388
  end
332
- request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
333
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
334
- end
335
- rescue Exception => e
336
- blockexception = e
337
389
  end
338
- end
339
- #########
340
-
341
- #OK, now we are out of the block passed to the lower level
342
- if(blockexception)
343
- raise blockexception
344
- end
345
- benchblock.xml.add! do
346
- parser.parse(responsehdr)
347
- end
348
- return parser.result
349
- end
350
- else
351
- benchblock.service.add!{ response = @connection.request(request) }
352
- # check response for errors...
353
- @last_response = response
354
- if response.is_a?(Net::HTTPSuccess)
355
- @error_handler = nil
356
- benchblock.xml.add! { parser.parse(response) }
357
- return parser.result
358
- else
359
- @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
360
- check_result = @error_handler.check(request)
361
- if check_result
390
+ rescue
362
391
  @error_handler = nil
363
- return check_result
364
- end
365
- request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
366
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
367
- end
368
- end
369
- rescue
370
- @error_handler = nil
371
- raise
372
- end
392
+ raise
393
+ end
373
394
 
374
- def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
375
- # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
376
- # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
377
- # If the caching is enabled and hit then throw AwsNoChange.
378
- # P.S. caching works for the whole images list only! (when the list param is blank)
379
- # check cache
380
- response, params = request_info(link, RightDummyParser.new)
381
- cache_hits?(method.to_sym, response.body) if use_cache
382
- parser = parser_class.new(:logger => @logger)
383
- benchblock.xml.add!{ parser.parse(response, params) }
384
- result = block_given? ? yield(parser) : parser.result
385
- # update parsed data
386
- update_cache(method.to_sym, :parsed => result) if use_cache
387
- result
388
- end
395
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
396
+ # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
397
+ # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
398
+ # If the caching is enabled and hit then throw AwsNoChange.
399
+ # P.S. caching works for the whole images list only! (when the list param is blank)
400
+ # check cache
401
+ response, params = request_info(link, RightDummyParser.new)
402
+ cache_hits?(method.to_sym, response.body) if use_cache
403
+ parser = parser_class.new(:logger => @logger)
404
+ benchblock.xml.add!{ parser.parse(response, params) }
405
+ result = block_given? ? yield(parser) : parser.result
406
+ # update parsed data
407
+ update_cache(method.to_sym, :parsed => result) if use_cache
408
+ result
409
+ end
410
+
411
+ # Returns Amazons request ID for the latest request
412
+ def last_request_id
413
+ @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}] && $1
414
+ end
389
415
 
390
- # Returns Amazons request ID for the latest request
391
- def last_request_id
392
- @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}] && $1
393
416
  end
394
417
 
395
- end
396
418
 
419
+ # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
420
+ # web services raise this type of error.
421
+ # Attribute inherited by RuntimeError:
422
+ # message - the text of the error, generally as returned by AWS in its XML response.
423
+ class AwsError < RuntimeError
397
424
 
398
- # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
399
- # web services raise this type of error.
400
- # Attribute inherited by RuntimeError:
401
- # message - the text of the error, generally as returned by AWS in its XML response.
402
- class AwsError < RuntimeError
425
+ # either an array of errors where each item is itself an array of [code, message]),
426
+ # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
427
+ attr_reader :errors
403
428
 
404
- # either an array of errors where each item is itself an array of [code, message]),
405
- # or an error string if the error was raised manually, as in <tt>AwsError.new('err_text')</tt>
406
- attr_reader :errors
429
+ # Request id (if exists)
430
+ attr_reader :request_id
407
431
 
408
- # Request id (if exists)
409
- attr_reader :request_id
432
+ # Response HTTP error code
433
+ attr_reader :http_code
410
434
 
411
- # Response HTTP error code
412
- attr_reader :http_code
435
+ # Raw request text data to AWS
436
+ attr_reader :request_data
413
437
 
414
- # Raw request text data to AWS
415
- attr_reader :request_data
438
+ def initialize(errors=nil, http_code=nil, request_id=nil, request_data=nil)
439
+ @errors = errors
440
+ @request_id = request_id
441
+ @http_code = http_code
442
+ @request_data = request_data
443
+ msg = @errors.is_a?(Array) ? @errors.map{|code, msg| "#{code}: #{msg}"}.join("; ") : @errors.to_s
444
+ msg += "\nREQUEST(#{@request_data})" unless @request_data.nil?
445
+ super(msg)
446
+ end
416
447
 
417
- def initialize(errors=nil, http_code=nil, request_id=nil, request_data=nil)
418
- @errors = errors
419
- @request_id = request_id
420
- @http_code = http_code
421
- @request_data = request_data
422
- msg = @errors.is_a?(Array) ? @errors.map{|code, msg| "#{code}: #{msg}"}.join("; ") : @errors.to_s
423
- msg += "\nREQUEST(#{@request_data})" unless @request_data.nil?
424
- super(msg)
425
- end
448
+ # Does any of the error messages include the regexp +pattern+?
449
+ # Used to determine whether to retry request.
450
+ def include?(pattern)
451
+ if @errors.is_a?(Array)
452
+ @errors.each{ |code, msg| return true if code =~ pattern }
453
+ else
454
+ return true if @errors_str =~ pattern
455
+ end
456
+ false
457
+ end
426
458
 
427
- # Does any of the error messages include the regexp +pattern+?
428
- # Used to determine whether to retry request.
429
- def include?(pattern)
430
- if @errors.is_a?(Array)
431
- @errors.each{ |code, msg| return true if code =~ pattern }
432
- else
433
- return true if @errors_str =~ pattern
434
- end
435
- false
436
- end
459
+ # Generic handler for AwsErrors. +aws+ is the RightAws::S3, RightAws::EC2, or RightAws::SQS
460
+ # object that caused the exception (it must provide last_request and last_response). Supported
461
+ # boolean options are:
462
+ # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
463
+ # * <tt>:puts</tt> do a "puts" of the error
464
+ # * <tt>:raise</tt> re-raise the error after logging
465
+ def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
466
+ # Only log & notify if not user error
467
+ if !options[:raise] || system_error?($!)
468
+ error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
469
+ puts error_text if options[:puts]
470
+ # Log the error
471
+ if options[:log]
472
+ request = aws.last_request ? aws.last_request.path : '-none-'
473
+ response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
474
+ aws.logger.error error_text
475
+ aws.logger.error "Request was: #{request}"
476
+ aws.logger.error "Response was: #{response}"
477
+ end
478
+ end
479
+ raise if options[:raise] # re-raise an exception
480
+ return nil
481
+ end
437
482
 
438
- # Generic handler for AwsErrors. +aws+ is the RightAws::S3, RightAws::EC2, or RightAws::SQS
439
- # object that caused the exception (it must provide last_request and last_response). Supported
440
- # boolean options are:
441
- # * <tt>:log</tt> print a message into the log using aws.logger to access the Logger
442
- # * <tt>:puts</tt> do a "puts" of the error
443
- # * <tt>:raise</tt> re-raise the error after logging
444
- def self.on_aws_exception(aws, options={:raise=>true, :log=>true})
445
- # Only log & notify if not user error
446
- if !options[:raise] || system_error?($!)
447
- error_text = "#{$!.inspect}\n#{$@}.join('\n')}"
448
- puts error_text if options[:puts]
449
- # Log the error
450
- if options[:log]
451
- request = aws.last_request ? aws.last_request.path : '-none-'
452
- response = aws.last_response ? "#{aws.last_response.code} -- #{aws.last_response.message} -- #{aws.last_response.body}" : '-none-'
453
- aws.logger.error error_text
454
- aws.logger.error "Request was: #{request}"
455
- aws.logger.error "Response was: #{response}"
456
- end
457
- end
458
- raise if options[:raise] # re-raise an exception
459
- return nil
460
- end
483
+ # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
484
+ # Used to force logging.
485
+ def self.system_error?(e)
486
+ !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
487
+ end
461
488
 
462
- # True if e is an AWS system error, i.e. something that is for sure not the caller's fault.
463
- # Used to force logging.
464
- def self.system_error?(e)
465
- !e.is_a?(self) || e.message =~ /InternalError|InsufficientInstanceCapacity|Unavailable/
466
489
  end
467
490
 
468
- end
469
491
 
492
+ class AWSErrorHandler
493
+ # 0-100 (%)
494
+ DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
470
495
 
471
- class AWSErrorHandler
472
- # 0-100 (%)
473
- DEFAULT_CLOSE_ON_4XX_PROBABILITY = 10
496
+ @@reiteration_start_delay = 0.2
497
+ def self.reiteration_start_delay
498
+ @@reiteration_start_delay
499
+ end
500
+ def self.reiteration_start_delay=(reiteration_start_delay)
501
+ @@reiteration_start_delay = reiteration_start_delay
502
+ end
474
503
 
475
- @@reiteration_start_delay = 0.2
476
- def self.reiteration_start_delay
477
- @@reiteration_start_delay
478
- end
479
- def self.reiteration_start_delay=(reiteration_start_delay)
480
- @@reiteration_start_delay = reiteration_start_delay
481
- end
504
+ @@reiteration_time = 5
505
+ def self.reiteration_time
506
+ @@reiteration_time
507
+ end
508
+ def self.reiteration_time=(reiteration_time)
509
+ @@reiteration_time = reiteration_time
510
+ end
482
511
 
483
- @@reiteration_time = 5
484
- def self.reiteration_time
485
- @@reiteration_time
486
- end
487
- def self.reiteration_time=(reiteration_time)
488
- @@reiteration_time = reiteration_time
489
- end
512
+ @@close_on_error = true
513
+ def self.close_on_error
514
+ @@close_on_error
515
+ end
516
+ def self.close_on_error=(close_on_error)
517
+ @@close_on_error = close_on_error
518
+ end
490
519
 
491
- @@close_on_error = true
492
- def self.close_on_error
493
- @@close_on_error
494
- end
495
- def self.close_on_error=(close_on_error)
496
- @@close_on_error = close_on_error
497
- end
520
+ @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
521
+ def self.close_on_4xx_probability
522
+ @@close_on_4xx_probability
523
+ end
524
+ def self.close_on_4xx_probability=(close_on_4xx_probability)
525
+ @@close_on_4xx_probability = close_on_4xx_probability
526
+ end
498
527
 
499
- @@close_on_4xx_probability = DEFAULT_CLOSE_ON_4XX_PROBABILITY
500
- def self.close_on_4xx_probability
501
- @@close_on_4xx_probability
502
- end
503
- def self.close_on_4xx_probability=(close_on_4xx_probability)
504
- @@close_on_4xx_probability = close_on_4xx_probability
505
- end
528
+ # params:
529
+ # :reiteration_time
530
+ # :errors_list
531
+ # :close_on_error = true | false
532
+ # :close_on_4xx_probability = 1-100
533
+ def initialize(aws, parser, params={}) #:nodoc:
534
+ @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
535
+ @parser = parser # parser to parse Amazon response
536
+ @started_at = Time.now
537
+ @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
538
+ @errors_list = params[:errors_list] || []
539
+ @reiteration_delay = @@reiteration_start_delay
540
+ @retries = 0
541
+ # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
542
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
543
+ @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
544
+ end
506
545
 
507
- # params:
508
- # :reiteration_time
509
- # :errors_list
510
- # :close_on_error = true | false
511
- # :close_on_4xx_probability = 1-100
512
- def initialize(aws, parser, params={}) #:nodoc:
513
- @aws = aws # Link to RightEc2 | RightSqs | RightS3 instance
514
- @parser = parser # parser to parse Amazon response
515
- @started_at = Time.now
516
- @stop_at = @started_at + (params[:reiteration_time] || @@reiteration_time)
517
- @errors_list = params[:errors_list] || []
518
- @reiteration_delay = @@reiteration_start_delay
519
- @retries = 0
520
- # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
521
- @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
522
- @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
523
- end
546
+ # Returns false if
547
+ def check(request) #:nodoc:
548
+ result = false
549
+ error_found = false
550
+ redirect_detected= false
551
+ error_match = nil
552
+ last_errors_text = ''
553
+ response = @aws.last_response
554
+ # log error
555
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
556
+ # is this a redirect?
557
+ # yes!
558
+ if response.is_a?(Net::HTTPRedirection)
559
+ redirect_detected = true
560
+ else
561
+ # no, it's an error ...
562
+ @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
563
+ @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
564
+ end
565
+ # Check response body: if it is an Amazon XML document or not:
566
+ if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
567
+ @aws.class.bench_xml.add! do
568
+ error_parser = RightErrorResponseParser.new
569
+ error_parser.parse(response)
570
+ @aws.last_errors = error_parser.errors
571
+ @aws.last_request_id = error_parser.requestID
572
+ last_errors_text = @aws.last_errors.flatten.join("\n")
573
+ # on redirect :
574
+ if redirect_detected
575
+ location = response['location']
576
+ # ... log information and ...
577
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
578
+ @aws.logger.info("##### New location: #{location} #####")
579
+ # ... fix the connection data
580
+ request[:server] = URI.parse(location).host
581
+ request[:protocol] = URI.parse(location).scheme
582
+ request[:port] = URI.parse(location).port
583
+ end
584
+ end
585
+ else # ... it is not a xml document(probably just a html page?)
586
+ @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
587
+ @aws.last_request_id = '-undefined-'
588
+ last_errors_text = response.message
589
+ end
590
+ # now - check the error
591
+ unless redirect_detected
592
+ @errors_list.each do |error_to_find|
593
+ if last_errors_text[/#{error_to_find}/i]
594
+ error_found = true
595
+ error_match = error_to_find
596
+ @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
597
+ break
598
+ end
599
+ end
600
+ end
601
+ # check the time has gone from the first error come
602
+ if redirect_detected || error_found
603
+ # Close the connection to the server and recreate a new one.
604
+ # It may have a chance that one server is a semi-down and reconnection
605
+ # will help us to connect to the other server
606
+ if !redirect_detected && @close_on_error
607
+ @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
608
+ end
524
609
 
525
- # Returns false if
526
- def check(request) #:nodoc:
527
- result = false
528
- error_found = false
529
- redirect_detected= false
530
- error_match = nil
531
- last_errors_text = ''
532
- response = @aws.last_response
533
- # log error
534
- request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
535
- # is this a redirect?
536
- # yes!
537
- if response.is_a?(Net::HTTPRedirection)
538
- redirect_detected = true
539
- else
540
- # no, it's an error ...
541
- @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
542
- @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
543
- end
544
- # Check response body: if it is an Amazon XML document or not:
545
- if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
546
- @aws.class.bench_xml.add! do
547
- error_parser = RightErrorResponseParser.new
548
- error_parser.parse(response)
549
- @aws.last_errors = error_parser.errors
550
- @aws.last_request_id = error_parser.requestID
551
- last_errors_text = @aws.last_errors.flatten.join("\n")
552
- # on redirect :
553
- if redirect_detected
554
- location = response['location']
555
- # ... log information and ...
556
- @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
557
- @aws.logger.info("##### New location: #{location} #####")
558
- # ... fix the connection data
559
- request[:server] = URI.parse(location).host
560
- request[:protocol] = URI.parse(location).scheme
561
- request[:port] = URI.parse(location).port
562
- end
563
- end
564
- else # ... it is not a xml document(probably just a html page?)
565
- @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
566
- @aws.last_request_id = '-undefined-'
567
- last_errors_text = response.message
568
- end
569
- # now - check the error
570
- unless redirect_detected
571
- @errors_list.each do |error_to_find|
572
- if last_errors_text[/#{error_to_find}/i]
573
- error_found = true
574
- error_match = error_to_find
575
- @aws.logger.warn("##### Retry is needed, error pattern match: #{error_to_find} #####")
576
- break
577
- end
578
- end
579
- end
580
- # check the time has gone from the first error come
581
- if redirect_detected || error_found
582
- # Close the connection to the server and recreate a new one.
583
- # It may have a chance that one server is a semi-down and reconnection
584
- # will help us to connect to the other server
585
- if !redirect_detected && @close_on_error
586
- @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
587
- end
588
-
589
- if (Time.now < @stop_at)
590
- @retries += 1
591
- unless redirect_detected
592
- @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
593
- sleep @reiteration_delay
594
- @reiteration_delay *= 2
595
-
596
- # Always make sure that the fp is set to point to the beginning(?)
597
- # of the File/IO. TODO: it assumes that offset is 0, which is bad.
598
- if(request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
599
- begin
600
- request[:request].body_stream.pos = 0
601
- rescue Exception => e
602
- @logger.warn("Retry may fail due to unable to reset the file pointer" +
603
- " -- #{self.class.name} : #{e.inspect}")
604
- end
610
+ if (Time.now < @stop_at)
611
+ @retries += 1
612
+ unless redirect_detected
613
+ @aws.logger.warn("##### Retry ##{@retries} is being performed. Sleeping for #{@reiteration_delay} sec. Whole time: #{Time.now-@started_at} sec ####")
614
+ sleep @reiteration_delay
615
+ @reiteration_delay *= 2
616
+
617
+ # Always make sure that the fp is set to point to the beginning(?)
618
+ # of the File/IO. TODO: it assumes that offset is 0, which is bad.
619
+ if (request[:request].body_stream && request[:request].body_stream.respond_to?(:pos))
620
+ begin
621
+ request[:request].body_stream.pos = 0
622
+ rescue Exception => e
623
+ @logger.warn("Retry may fail due to unable to reset the file pointer" +
624
+ " -- #{self.class.name} : #{e.inspect}")
625
+ end
626
+ end
627
+ else
628
+ @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
629
+ end
630
+ result = @aws.request_info(request, @parser)
631
+ else
632
+ @aws.logger.warn("##### Ooops, time is over... ####")
633
+ end
634
+ # aha, this is unhandled error:
635
+ elsif @close_on_error
636
+ # Is this a 5xx error ?
637
+ if @aws.last_response.code.to_s[/^5\d\d$/]
638
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
639
+ # Is this a 4xx error ?
640
+ elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
641
+ @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
642
+ "probability: #{@close_on_4xx_probability}%"
643
+ end
605
644
  end
606
- else
607
- @aws.logger.info("##### Retry ##{@retries} is being performed due to a redirect. ####")
608
- end
609
- result = @aws.request_info(request, @parser)
610
- else
611
- @aws.logger.warn("##### Ooops, time is over... ####")
612
- end
613
- # aha, this is unhandled error:
614
- elsif @close_on_error
615
- # Is this a 5xx error ?
616
- if @aws.last_response.code.to_s[/^5\d\d$/]
617
- @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
618
- # Is this a 4xx error ?
619
- elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
620
- @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
621
- "probability: #{@close_on_4xx_probability}%"
622
- end
623
- end
624
- result
645
+ result
646
+ end
647
+
625
648
  end
626
649
 
627
- end
628
650
 
651
+ #-----------------------------------------------------------------
629
652
 
630
- #-----------------------------------------------------------------
653
+ class RightSaxParserCallback #:nodoc:
654
+ def self.include_callback
655
+ include XML::SaxParser::Callbacks
656
+ end
631
657
 
632
- class RightSaxParserCallback #:nodoc:
633
- def self.include_callback
634
- include XML::SaxParser::Callbacks
635
- end
636
- def initialize(right_aws_parser)
637
- @right_aws_parser = right_aws_parser
638
- end
639
- def on_start_element(name, attr_hash)
640
- @right_aws_parser.tag_start(name, attr_hash)
641
- end
642
- def on_characters(chars)
643
- @right_aws_parser.text(chars)
644
- end
645
- def on_end_element(name)
646
- @right_aws_parser.tag_end(name)
647
- end
648
- def on_start_document; end
649
- def on_comment(msg); end
650
- def on_processing_instruction(target, data); end
651
- def on_cdata_block(cdata); end
652
- def on_end_document; end
653
- end
654
-
655
- class RightAWSParser #:nodoc:
656
- # default parsing library
657
- DEFAULT_XML_LIBRARY = 'rexml'
658
- # a list of supported parsers
659
- @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml']
660
-
661
- @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml'
662
- def self.xml_lib
663
- @@xml_lib
664
- end
665
- def self.xml_lib=(new_lib_name)
666
- @@xml_lib = new_lib_name
667
- end
658
+ def initialize(right_aws_parser)
659
+ @right_aws_parser = right_aws_parser
660
+ end
668
661
 
669
- attr_accessor :result
670
- attr_reader :xmlpath
671
- attr_accessor :xml_lib
672
-
673
- def initialize(params={})
674
- @xmlpath = ''
675
- @result = false
676
- @text = ''
677
- @xml_lib = params[:xml_lib] || @@xml_lib
678
- @logger = params[:logger]
679
- reset
680
- end
681
- def tag_start(name, attributes)
682
- @text = ''
683
- tagstart(name, attributes)
684
- @xmlpath += @xmlpath.empty? ? name : "/#{name}"
685
- end
686
- def tag_end(name)
687
- if @xmlpath =~ /^(.*?)\/?#{name}$/
688
- @xmlpath = $1
689
- end
690
- tagend(name)
691
- end
692
- def text(text)
693
- @text += text
694
- tagtext(text)
695
- end
696
- # Parser method.
697
- # Params:
698
- # xml_text - xml message text(String) or Net:HTTPxxx instance (response)
699
- # params[:xml_lib] - library name: 'rexml' | 'libxml'
700
- def parse(xml_text, params={})
701
- # Get response body
702
- xml_text = xml_text.body unless xml_text.is_a?(String)
703
- @xml_lib = params[:xml_lib] || @xml_lib
704
- # check that we had no problems with this library otherwise use default
705
- @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib)
706
- # load xml library
707
- if @xml_lib=='libxml' && !defined?(XML::SaxParser)
708
- begin
709
- require 'xml/libxml'
710
- # is it new ? - Setup SaxParserCallback
711
- if XML::Parser::VERSION >= '0.5.1.0'
712
- RightSaxParserCallback.include_callback
713
- end
714
- rescue LoadError => e
715
- @@supported_xml_libs.delete(@xml_lib)
716
- @xml_lib = DEFAULT_XML_LIBRARY
717
- if @logger
718
- @logger.error e.inspect
719
- @logger.error e.backtrace
720
- @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
721
- end
722
- end
723
- end
724
- # Parse the xml text
725
- case @xml_lib
726
- when 'libxml'
727
- xml = XML::SaxParser.new
728
- xml.string = xml_text
729
- # check libxml-ruby version
730
- if XML::Parser::VERSION >= '0.5.1.0'
731
- xml.callbacks = RightSaxParserCallback.new(self)
732
- else
733
- xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
734
- xml.on_characters{ |text| self.text(text)}
735
- xml.on_end_element{ |name| self.tag_end(name)}
736
- end
737
- xml.parse
738
- else
739
- REXML::Document.parse_stream(xml_text, self)
740
- end
662
+ def on_start_element(name, attr_hash)
663
+ @right_aws_parser.tag_start(name, attr_hash)
664
+ end
665
+
666
+ def on_characters(chars)
667
+ @right_aws_parser.text(chars)
668
+ end
669
+
670
+ def on_end_element(name)
671
+ @right_aws_parser.tag_end(name)
672
+ end
673
+
674
+ def on_start_document;
675
+ end
676
+
677
+ def on_comment(msg)
678
+ ;
679
+ end
680
+
681
+ def on_processing_instruction(target, data)
682
+ ;
683
+ end
684
+
685
+ def on_cdata_block(cdata)
686
+ ;
687
+ end
688
+
689
+ def on_end_document;
690
+ end
741
691
  end
742
- # Parser must have a lots of methods
743
- # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb)
744
- # We dont need most of them in RightAWSParser and method_missing helps us
745
- # to skip their definition
746
- def method_missing(method, *params)
747
- # if the method is one of known - just skip it ...
748
- return if [:comment, :attlistdecl, :notationdecl, :elementdecl,
749
- :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction,
750
- :doctype].include?(method)
751
- # ... else - call super to raise an exception
752
- super(method, params)
692
+
693
+ class RightAWSParser #:nodoc:
694
+ # default parsing library
695
+ DEFAULT_XML_LIBRARY = 'rexml'
696
+ # a list of supported parsers
697
+ @@supported_xml_libs = [DEFAULT_XML_LIBRARY, 'libxml']
698
+
699
+ @@xml_lib = DEFAULT_XML_LIBRARY # xml library name: 'rexml' | 'libxml'
700
+ def self.xml_lib
701
+ @@xml_lib
702
+ end
703
+ def self.xml_lib=(new_lib_name)
704
+ @@xml_lib = new_lib_name
705
+ end
706
+
707
+ attr_accessor :result
708
+ attr_reader :xmlpath
709
+ attr_accessor :xml_lib
710
+
711
+ def initialize(params={})
712
+ @xmlpath = ''
713
+ @result = false
714
+ @text = ''
715
+ @xml_lib = params[:xml_lib] || @@xml_lib
716
+ @logger = params[:logger]
717
+ reset
718
+ end
719
+
720
+ def tag_start(name, attributes)
721
+ @text = ''
722
+ tagstart(name, attributes)
723
+ @xmlpath += @xmlpath.empty? ? name : "/#{name}"
724
+ end
725
+
726
+ def tag_end(name)
727
+ if @xmlpath =~ /^(.*?)\/?#{name}$/
728
+ @xmlpath = $1
729
+ end
730
+ tagend(name)
731
+ end
732
+
733
+ def text(text)
734
+ @text += text
735
+ tagtext(text)
736
+ end
737
+
738
+ # Parser method.
739
+ # Params:
740
+ # xml_text - xml message text(String) or Net:HTTPxxx instance (response)
741
+ # params[:xml_lib] - library name: 'rexml' | 'libxml'
742
+ def parse(xml_text, params={})
743
+ # Get response body
744
+ unless xml_text.is_a?(String)
745
+ xml_text = xml_text.body.respond_to?(:force_encoding) ? xml_text.body.force_encoding("UTF-8") : xml_text.body
746
+ end
747
+
748
+ @xml_lib = params[:xml_lib] || @xml_lib
749
+ # check that we had no problems with this library otherwise use default
750
+ @xml_lib = DEFAULT_XML_LIBRARY unless @@supported_xml_libs.include?(@xml_lib)
751
+ # load xml library
752
+ if @xml_lib=='libxml' && !defined?(XML::SaxParser)
753
+ begin
754
+ require 'xml/libxml'
755
+ # is it new ? - Setup SaxParserCallback
756
+ if XML::Parser::VERSION >= '0.5.1.0'
757
+ RightSaxParserCallback.include_callback
758
+ end
759
+ rescue LoadError => e
760
+ @@supported_xml_libs.delete(@xml_lib)
761
+ @xml_lib = DEFAULT_XML_LIBRARY
762
+ if @logger
763
+ @logger.error e.inspect
764
+ @logger.error e.backtrace
765
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
766
+ end
767
+ end
768
+ end
769
+ # Parse the xml text
770
+ case @xml_lib
771
+ when 'libxml'
772
+ xml = XML::SaxParser.new
773
+ xml.string = xml_text
774
+ # check libxml-ruby version
775
+ if XML::Parser::VERSION >= '0.5.1.0'
776
+ xml.callbacks = RightSaxParserCallback.new(self)
777
+ else
778
+ xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
779
+ xml.on_characters{ |text| self.text(text)}
780
+ xml.on_end_element{ |name| self.tag_end(name)}
781
+ end
782
+ xml.parse
783
+ else
784
+ REXML::Document.parse_stream(xml_text, self)
785
+ end
786
+ end
787
+
788
+ # Parser must have a lots of methods
789
+ # (see /usr/lib/ruby/1.8/rexml/parsers/streamparser.rb)
790
+ # We dont need most of them in RightAWSParser and method_missing helps us
791
+ # to skip their definition
792
+ def method_missing(method, *params)
793
+ # if the method is one of known - just skip it ...
794
+ return if [:comment, :attlistdecl, :notationdecl, :elementdecl,
795
+ :entitydecl, :cdata, :xmldecl, :attlistdecl, :instruction,
796
+ :doctype].include?(method)
797
+ # ... else - call super to raise an exception
798
+ super(method, params)
799
+ end
800
+
801
+ # the functions to be overriden by children (if nessesery)
802
+ def reset;
803
+ end
804
+
805
+ def tagstart(name, attributes)
806
+ ;
807
+ end
808
+
809
+ def tagend(name)
810
+ ;
811
+ end
812
+
813
+ def tagtext(text)
814
+ ;
815
+ end
753
816
  end
754
- # the functions to be overriden by children (if nessesery)
755
- def reset ; end
756
- def tagstart(name, attributes); end
757
- def tagend(name) ; end
758
- def tagtext(text) ; end
759
- end
760
817
 
761
- #-----------------------------------------------------------------
762
- # PARSERS: Errors
763
- #-----------------------------------------------------------------
818
+ #-----------------------------------------------------------------
819
+ # PARSERS: Errors
820
+ #-----------------------------------------------------------------
764
821
 
765
822
  #<Error>
766
823
  # <Code>TemporaryRedirect</Code>
@@ -771,40 +828,46 @@ module RightAws
771
828
  # <Bucket>bucket-for-k</Bucket>
772
829
  #</Error>
773
830
 
774
- class RightErrorResponseParser < RightAWSParser #:nodoc:
775
- attr_accessor :errors # array of hashes: error/message
776
- attr_accessor :requestID
831
+ class RightErrorResponseParser < RightAWSParser #:nodoc:
832
+ attr_accessor :errors # array of hashes: error/message
833
+ attr_accessor :requestID
777
834
  # attr_accessor :endpoint, :host_id, :bucket
778
- def tagend(name)
779
- case name
780
- when 'RequestID' ; @requestID = @text
781
- when 'Code' ; @code = @text
782
- when 'Message' ; @message = @text
835
+ def tagend(name)
836
+ case name
837
+ when 'RequestID';
838
+ @requestID = @text
839
+ when 'Code';
840
+ @code = @text
841
+ when 'Message';
842
+ @message = @text
783
843
  # when 'Endpoint' ; @endpoint = @text
784
844
  # when 'HostId' ; @host_id = @text
785
845
  # when 'Bucket' ; @bucket = @text
786
- when 'Error' ; @errors << [ @code, @message ]
787
- end
788
- end
789
- def reset
790
- @errors = []
846
+ when 'Error';
847
+ @errors << [ @code, @message ]
848
+ end
849
+ end
850
+
851
+ def reset
852
+ @errors = []
853
+ end
791
854
  end
792
- end
793
-
794
- # Dummy parser - does nothing
795
- # Returns the original params back
796
- class RightDummyParser # :nodoc:
797
- attr_accessor :result
798
- def parse(response, params={})
799
- @result = [response, params]
855
+
856
+ # Dummy parser - does nothing
857
+ # Returns the original params back
858
+ class RightDummyParser # :nodoc:
859
+ attr_accessor :result
860
+
861
+ def parse(response, params={})
862
+ @result = [response, params]
863
+ end
800
864
  end
801
- end
802
865
 
803
- class RightHttp2xxParser < RightAWSParser # :nodoc:
804
- def parse(response)
805
- @result = response.is_a?(Net::HTTPSuccess)
866
+ class RightHttp2xxParser < RightAWSParser # :nodoc:
867
+ def parse(response)
868
+ @result = response.is_a?(Net::HTTPSuccess)
869
+ end
806
870
  end
807
- end
808
871
 
809
872
  end
810
873