aws 1.11.9 → 1.11.36

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