right_aws 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -47,4 +47,20 @@ r1581 | tve | 2007-08-24 16:21:45 -0700 (Fri, 24 Aug 2007) | 1 line
47
47
  Improved RightAws documentation
48
48
  ------------------------------------------------------------------------
49
49
 
50
+ == 1.3.0 / 2007-09-26
51
+ ------------------------------------------------------------------------
52
+ r1754 | todd | 2007-09-19 13:48:34 -0700 (Wed, 19 Sep 2007) | 6 lines
53
+
54
+ (#487, #488) Consolidate a lot of code that was repeated in three places.
55
+ Fix error handling path when using streaming GET interfaces with S3.
56
+ Also add a stub for RightHttpConnection which
57
+ allows more control over the unit tests. Expand the unit tests and coverage
58
+ tests.
59
+ ------------------------------------------------------------------------
60
+ r1755 | todd | 2007-09-19 14:29:19 -0700 (Wed, 19 Sep 2007) | 2 lines
61
+
62
+ (#487) RDoc fixes after code consolidation
63
+
64
+ ------------------------------------------------------------------------
65
+
50
66
 
@@ -15,8 +15,10 @@ test/ec2/test_helper.rb
15
15
  test/ec2/test_right_ec2.rb
16
16
  test/s3/test_helper.rb
17
17
  test/s3/test_right_s3.rb
18
+ test/s3/test_right_s3_stubbed.rb
18
19
  test/sqs/test_helper.rb
19
20
  test/sqs/test_right_sqs.rb
20
21
  test/ts_right_aws.rb
21
22
  test/test_credentials.rb
23
+ test/http_connection.rb
22
24
 
data/Rakefile CHANGED
@@ -7,6 +7,8 @@ require 'rcov/rcovtask'
7
7
  $: << File.dirname(__FILE__)
8
8
  require 'lib/right_aws.rb'
9
9
 
10
+ testglobs = ["test/ts_right_aws.rb"]
11
+
10
12
  Hoe.new('right_aws', RightAws::VERSION::STRING) do |p|
11
13
  p.rubyforge_name = 'rightaws'
12
14
  p.author = 'RightScale, Inc.'
@@ -17,14 +19,44 @@ Hoe.new('right_aws', RightAws::VERSION::STRING) do |p|
17
19
  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
18
20
  p.remote_rdoc_dir = "/right_aws_gem_doc"
19
21
  p.extra_deps = [['right_http_connection','>= 0.1.4']]
20
- p.test_globs = ['test/ts_right_aws.rb']
22
+ p.test_globs = testglobs
21
23
  end
22
24
 
23
25
  desc "Analyze code coverage of the unit tests."
24
26
  Rcov::RcovTask.new do |t|
25
- t.test_files = FileList["test/ts*.rb"]
27
+ t.test_files = FileList[testglobs]
26
28
  #t.verbose = true # uncomment to see the executed command
27
29
  end
30
+
31
+ desc "Test just the SQS interface"
32
+ task :testsqs do
33
+ require 'test/test_credentials'
34
+ require 'test/http_connection'
35
+ TestCredentials.get_credentials
36
+ require 'test/sqs/test_right_sqs.rb'
37
+ end
38
+
39
+ desc "Test just the S3 interface"
40
+ task :tests3 do
41
+ require 'test/test_credentials'
42
+ require 'test/http_connection'
43
+ TestCredentials.get_credentials
44
+ require 'test/s3/test_right_s3.rb'
45
+ end
28
46
 
47
+ desc "Test just the S3 interface using local stubs"
48
+ task :tests3local do
49
+ require 'test/test_credentials'
50
+ require 'test/http_connection'
51
+ TestCredentials.get_credentials
52
+ require 'test/s3/test_right_s3_stubbed.rb'
53
+ end
54
+
55
+ desc "Test just the EC2 interface"
56
+ task :testec2 do
57
+ require 'test/test_credentials'
58
+ TestCredentials.get_credentials
59
+ require 'test/ec2/test_right_ec2.rb'
60
+ end
29
61
 
30
62
  # vim: syntax=Ruby
@@ -25,7 +25,7 @@
25
25
 
26
26
  # A hack because there's a bug in add! in Benchmark::Tms
27
27
  module Benchmark #:nodoc:
28
- class Tms
28
+ class Tms #:nodoc:
29
29
  def add!(&blk)
30
30
  t = Benchmark::measure(&blk)
31
31
  @utime = utime + t.utime
@@ -24,9 +24,30 @@
24
24
  # Test
25
25
  module RightAws
26
26
 
27
- # Text, if found in an error message returned by AWS, indicates that this may be a transient
28
- # error. Transient errors are automatically retried with exponential back-off.
29
- AMAZON_PROBLEMS = [ 'internal service error',
27
+ class AwsUtils #:nodoc:
28
+ def self.sign(aws_secret_access_key, auth_string)
29
+ Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new("sha1"), aws_secret_access_key, auth_string)).strip
30
+ end
31
+
32
+ end
33
+
34
+ class AwsBenchmarkingBlock #:nodoc:
35
+ attr_accessor :xml, :service
36
+ def initialize
37
+ # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
38
+ @service = Benchmark::Tms.new()
39
+ # Benchmark::Tms instance for XML parsing benchmarking.
40
+ @xml = Benchmark::Tms.new()
41
+ end
42
+ end
43
+
44
+ class RightAwsBase
45
+
46
+ # Amazon HTTP Error handling
47
+
48
+ # Text, if found in an error message returned by AWS, indicates that this may be a transient
49
+ # error. Transient errors are automatically retried with exponential back-off.
50
+ AMAZON_PROBLEMS = [ 'internal service error',
30
51
  'is currently unavailable',
31
52
  'no response from',
32
53
  'Please try again',
@@ -36,12 +57,141 @@ module RightAws
36
57
  'This application is not currently available',
37
58
  'InsufficientInstanceCapacity'
38
59
  ]
60
+ @@amazon_problems = AMAZON_PROBLEMS
61
+ # Returns a list of Amazon service responses which are known to be transient problems.
62
+ # We have to re-request if we get any of them, because the problem will probably disappear.
63
+ # By default this method returns the same value as the AMAZON_PROBLEMS const.
64
+ def self.amazon_problems
65
+ @@amazon_problems
66
+ end
67
+
68
+ # Sets the list of Amazon side problems. Use in conjunction with the
69
+ # getter to append problems.
70
+ def self.amazon_problems=(problems_list)
71
+ @@amazon_problems = problems_list
72
+ end
73
+
74
+ end
75
+
76
+ module RightAwsBaseInterface
77
+
78
+ # Current aws_access_key_id
79
+ attr_reader :aws_access_key_id
80
+ # Last HTTP request object
81
+ attr_reader :last_request
82
+ # Last HTTP response object
83
+ attr_reader :last_response
84
+ # Last AWS errors list (used by AWSErrorHandler)
85
+ attr_accessor :last_errors
86
+ # Last AWS request id (used by AWSErrorHandler)
87
+ attr_accessor :last_request_id
88
+ # Logger object
89
+ attr_accessor :logger
90
+ # Initial params hash
91
+ attr_accessor :params
92
+
93
+ def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
94
+ @params = params
95
+ raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
96
+ if aws_access_key_id.blank? || aws_secret_access_key.blank?
97
+ @aws_access_key_id = aws_access_key_id
98
+ @aws_secret_access_key = aws_secret_access_key
99
+ @params[:server] ||= service_info[:default_host]
100
+ @params[:port] ||= service_info[:default_port]
101
+ @params[:protocol] ||= service_info[:default_protocol]
102
+ @params[:multi_thread] ||= defined?(AWS_DAEMON)
103
+ @logger = @params[:logger]
104
+ @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
105
+ @logger = Logger.new(STDOUT) if !@logger
106
+ @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
107
+ @error_handler = nil
108
+ end
109
+
110
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
111
+ AwsError::on_aws_exception(self, options)
112
+ end
113
+
114
+ # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
115
+ def multi_thread
116
+ @params[:multi_thread]
117
+ end
118
+
119
+ def request_info_impl(connection, benchblock, request, parser, &block) #:nodoc:
120
+ @last_request = request[:request]
121
+ @last_response = nil
122
+ response=nil
123
+ blockexception = nil
124
+
125
+ if(block != nil)
126
+ # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
127
+ # an exception may get thrown in the block body (which is high-level
128
+ # code either here or in the application) but gets caught in the
129
+ # low-level code of HttpConnection. The solution is not to let any
130
+ # exception escape the block that we pass to HttpConnection::request.
131
+ # Exceptions can originate from code directly in the block, or from user
132
+ # code called in the other block which is passed to response.read_body.
133
+ benchblock.service.add! do
134
+ responsehdr = connection.request(request) do |response|
135
+ #########
136
+ begin
137
+ @last_response = response
138
+ if response.is_a?(Net::HTTPSuccess)
139
+ @error_handler = nil
140
+ response.read_body(&block)
141
+ else
142
+ @error_handler = AWSErrorHandler.new(self, parser, self.class.amazon_problems) unless @error_handler
143
+ check_result = @error_handler.check(request)
144
+ if check_result
145
+ @error_handler = nil
146
+ return check_result
147
+ end
148
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
149
+ end
150
+ rescue Exception => e
151
+ blockexception = e
152
+ end
153
+ end
154
+ #########
155
+
156
+ #OK, now we are out of the block passed to the lower level
157
+ if(blockexception)
158
+ raise blockexception
159
+ end
160
+ benchblock.xml.add! do
161
+ parser.parse(responsehdr)
162
+ end
163
+ return parser.result
164
+ end
165
+ else
166
+ benchblock.service.add!{ response = connection.request(request) }
167
+ # check response for errors...
168
+ @last_response = response
169
+ if response.is_a?(Net::HTTPSuccess)
170
+ @error_handler = nil
171
+ benchblock.xml.add! { parser.parse(response) }
172
+ return parser.result
173
+ else
174
+ @error_handler = AWSErrorHandler.new(self, parser, self.class.amazon_problems) unless @error_handler
175
+ check_result = @error_handler.check(request)
176
+ if check_result
177
+ @error_handler = nil
178
+ return check_result
179
+ end
180
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
181
+ end
182
+ end
183
+ rescue
184
+ @error_handler = nil
185
+ raise
186
+ end
187
+
188
+ end
189
+
39
190
 
40
191
  # Exception class to signal any Amazon errors. All errors occuring during calls to Amazon's
41
192
  # web services raise this type of error.
42
193
  # Attribute inherited by RuntimeError:
43
194
  # message - the text of the error, generally as returned by AWS in its XML response.
44
-
45
195
  class AwsError < RuntimeError
46
196
 
47
197
  # either an array of errors where each item is itself an array of [code, message]),
@@ -63,7 +63,8 @@ module RightAws
63
63
  # Error handling: all operations raise an RightAws::AwsError in case
64
64
  # of problems. Note that transient errors are automatically retried.
65
65
 
66
- class Ec2
66
+ class Ec2 < RightAwsBase
67
+ include RightAwsBaseInterface
67
68
 
68
69
  SIGNATURE_VERSION = "1"
69
70
  # Amazon EC2 API version being used
@@ -75,43 +76,13 @@ module RightAws
75
76
  # Default addressing type (public=NAT, direct=no-NAT) used when launching instances.
76
77
  DEFAULT_ADDRESSING_TYPE = 'public'
77
78
  DNS_ADDRESSING_SET = ['public','direct']
78
-
79
- # A list of Amazon problems we can handle by AWSErrorHandler.
80
- @@amazon_problems = RightAws::AMAZON_PROBLEMS
81
-
82
- # Current aws_access_key_id
83
- attr_reader :aws_access_key_id
84
- # Last HTTP request object
85
- attr_reader :last_request
86
- # Last HTTP response object
87
- attr_reader :last_response
88
- # Last AWS errors list (used by AWSErrorHandler)
89
- attr_accessor :last_errors
90
- # Last AWS request id (used by AWSErrorHandler)
91
- attr_accessor :last_request_id
92
- # Logger object, used by this class to generate log messages
93
- attr_accessor :logger
94
- # Option params passed into new
95
- attr_accessor :params
96
-
97
- @@bench_ec2 = Benchmark::Tms.new()
98
- @@bench_xml = Benchmark::Tms.new()
99
-
100
- # Benchmark::Tms instance that accumulates time spent in requests to EC2.
101
- def self.bench_ec2; @@bench_ec2; end
102
-
103
- # Benchmark::Tms instance that accumulates time spent in XML parsing.
104
- def self.bench_xml; @@bench_xml; end
105
79
 
106
- # Returns a list of Amazon service responses which are known to be transient errors.
107
- # By default returns the same value as AMAZON_PROBLEMS const. See AWSErrorHandler.
108
- def self.amazon_problems
109
- @@amazon_problems
80
+ @@bench = AwsBenchmarkingBlock.new
81
+ def self.bench_xml
82
+ @@bench.xml
110
83
  end
111
-
112
- # Sets the list of Amazon problems that are automatically retried. See AWSErrorHandler.
113
- def self.amazon_problems=(problems_list)
114
- @@amazon_problems = problems_list
84
+ def self.bench_ec2
85
+ @@bench.service
115
86
  end
116
87
 
117
88
  # Create a new handle to an EC2 account. All handles share the same per process or per thread
@@ -124,31 +95,10 @@ module RightAws
124
95
  # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
125
96
  #
126
97
  def initialize(aws_access_key_id, aws_secret_access_key, params={})
127
- @params = params
128
- raise AwsError.new("AWS access keys are required to operate on EC2") \
129
- if aws_access_key_id.blank? || aws_secret_access_key.blank?
130
- @aws_access_key_id = aws_access_key_id
131
- @aws_secret_access_key = aws_secret_access_key
132
- # params
133
- @params[:server] ||= DEFAULT_HOST
134
- @params[:port] ||= DEFAULT_PORT
135
- @params[:protocol] ||= DEFAULT_PROTOCOL
136
- @params[:multi_thread] ||= defined?(AWS_DAEMON)
137
- # set logger
138
- @logger = @params[:logger]
139
- @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
140
- @logger = Logger.new(STDOUT) if !@logger
141
- @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
98
+ init({:name=>'EC2', :default_host => DEFAULT_HOST, :default_port => DEFAULT_PORT, :default_protocol => DEFAULT_PROTOCOL},
99
+ aws_access_key_id, aws_secret_access_key, params)
142
100
  end
143
101
 
144
- def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
145
- AwsError::on_aws_exception(self, options)
146
- end
147
-
148
- # Return +true+ if this RightEc2NativeQuery instance works in multi_thread mode and +false+ otherwise.
149
- def multi_thread
150
- @params[:multi_thread]
151
- end
152
102
 
153
103
  def generate_request(action, param={}) #:nodoc:
154
104
  timestamp = ( Time::now ).utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
@@ -159,7 +109,7 @@ module RightAws
159
109
  "SignatureVersion" => SIGNATURE_VERSION }
160
110
  request_hash.update(param)
161
111
  request_data = request_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
162
- request_hash.update('Signature' => Base64.encode64( OpenSSL::HMAC.digest( OpenSSL::Digest::Digest.new( "sha1" ), @aws_secret_access_key, request_data)).strip)
112
+ request_hash.update('Signature' => AwsUtils::sign(@aws_secret_access_key, request_data))
163
113
  request_params = request_hash.to_a.collect{|key,val| key + "=" + CGI::escape(val) }.join("&")
164
114
  request = Net::HTTP::Get.new("/?#{request_params}")
165
115
  # prepare output hash
@@ -174,29 +124,7 @@ module RightAws
174
124
  def request_info(request, parser) #:nodoc:
175
125
  thread = @params[:multi_thread] ? Thread.current : Thread.main
176
126
  thread[:ec2_connection] ||= Rightscale::HttpConnection.new(:exception => AwsError)
177
- @last_request = request[:request]
178
- @last_response = nil
179
- response=nil
180
-
181
- @@bench_ec2.add!{ response = thread[:ec2_connection].request(request) }
182
- # check response for errors...
183
- @last_response = response
184
- if response.is_a?(Net::HTTPSuccess)
185
- @error_handler = nil
186
- @@bench_xml.add! { parser.parse(response) }
187
- return parser.result
188
- else
189
- @error_handler = AWSErrorHandler.new(self, parser, @@amazon_problems) unless @error_handler
190
- check_result = @error_handler.check(request)
191
- if check_result
192
- @error_handler = nil
193
- return check_result
194
- end
195
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
196
- end
197
- rescue
198
- @error_handler = nil
199
- raise
127
+ request_info_impl(thread[:ec2_connection], @@bench, request, parser)
200
128
  end
201
129
 
202
130
 
@@ -48,7 +48,7 @@ require 'sqs/right_sqs'
48
48
  module RightAws #:nodoc:
49
49
  module VERSION #:nodoc:
50
50
  MAJOR = 1
51
- MINOR = 2
51
+ MINOR = 3
52
52
  TINY = 0
53
53
 
54
54
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -59,7 +59,7 @@ end
59
59
 
60
60
  # We also want everything available in the Rightscale namespace for backward
61
61
  # compatibility reasons.
62
- module Rightscale
62
+ module Rightscale #:nodoc:
63
63
  include RightAws
64
64
  extend RightAws
65
65
  end
@@ -23,7 +23,8 @@
23
23
 
24
24
  module RightAws
25
25
 
26
- class S3Interface
26
+ class S3Interface < RightAwsBase
27
+ include RightAwsBaseInterface
27
28
 
28
29
  DEFAULT_HOST = 's3.amazonaws.com'
29
30
  DEFAULT_PORT = 443
@@ -33,54 +34,15 @@ module RightAws
33
34
  AMAZON_HEADER_PREFIX = 'x-amz-'
34
35
  AMAZON_METADATA_PREFIX = 'x-amz-meta-'
35
36
 
36
- # A list if Amazons problems we can handle by AWSErrorHandler.
37
- @@amazon_problems = RightAws::AMAZON_PROBLEMS
38
-
39
- # TODO TRB 6/19/07 - all the below accessors are shared in the
40
- # three service gems. See if is it reasonable to stick these
41
- # in an interface class in right_awsbase that we can mixin
42
- #
43
- # Same for the benchmarking code - all three service gems have
44
- # the same. Break out into a helper class. Also look at the
45
- # benchmarking fix as a good thing to move to common code.
46
-
47
- # Current aws_access_key_id
48
- attr_reader :aws_access_key_id
49
- # Last HTTP request object
50
- attr_reader :last_request
51
- # Last HTTP response object
52
- attr_reader :last_response
53
- # Last AWS errors list (used by AWSErrorHandler)
54
- attr_accessor :last_errors
55
- # Last AWS request id (used by AWSErrorHandler)
56
- attr_accessor :last_request_id
57
- # Logger object
58
- attr_accessor :logger
59
- # Initial params hash
60
- attr_accessor :params
61
-
62
- @@bench_s3 = Benchmark::Tms.new()
63
- @@bench_xml = Benchmark::Tms.new()
64
-
65
- # Benchmark::Tms instance for S3 access benchmark.
66
- def self.bench_s3; @@bench_s3; end
67
-
68
- # Benchmark::Tms instance for XML parsing benchmark.
69
- def self.bench_xml; @@bench_xml; end # For benchmark puposes.
70
-
71
- # Returns a list of Amazon service responses which are known to be transient problems.
72
- # We have to re-request if we get any of them, because the problem will probably disappear.
73
- # By default this method returns the same value as the AMAZON_PROBLEMS const.
74
- def self.amazon_problems
75
- @@amazon_problems
37
+ @@bench = AwsBenchmarkingBlock.new
38
+ def self.bench_xml
39
+ @@bench.xml
76
40
  end
77
-
78
- # Sets the list of Amazon side problems. Use in conjunction with the
79
- # getter to append problems.
80
- def self.amazon_problems=(problems_list)
81
- @@amazon_problems = problems_list
41
+ def self.bench_s3
42
+ @@bench.service
82
43
  end
83
44
 
45
+
84
46
  # Creates new RightS3 instance.
85
47
  #
86
48
  # s3 = RightAws::S3Interface.new('1E3GDYEOGFJPIT7XXXXXX','hgTHt68JY07JKUY08ftHYtERkjgtfERn57XXXXXX', {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> #<RightS3:0xb7b3c27c>
@@ -94,39 +56,10 @@ module RightAws
94
56
  # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
95
57
  #
96
58
  def initialize(aws_access_key_id, aws_secret_access_key, params={})
97
- @params = params
98
- raise AwsError.new("AWS access keys are required to operate on S3") \
99
- if aws_access_key_id.blank? || aws_secret_access_key.blank?
100
-
101
- # TODO TRB 6/19/07 - keys, basic params, and logger are all
102
- # candidates to break out into a helper class common to all
103
- # service gems. Stick the helper in right_awsbase
104
- @aws_access_key_id = aws_access_key_id
105
- @aws_secret_access_key = aws_secret_access_key
106
- # params
107
- @params[:server] ||= DEFAULT_HOST
108
- @params[:port] ||= DEFAULT_PORT
109
- @params[:protocol] ||= DEFAULT_PROTOCOL
110
- @params[:multi_thread] ||= defined?(AWS_DAEMON)
111
- # set logger
112
- @logger = @params[:logger]
113
- @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
114
- @logger = Logger.new(STDOUT) if !@logger
115
- @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
116
- @error_handler = nil
117
- end
118
-
119
- # TODO TRB 6/19/07 - Service gem common method
120
- def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
121
- RightAws::AwsError::on_aws_exception(self, options)
59
+ init({:name=>'S3', :default_host => DEFAULT_HOST, :default_port => DEFAULT_PORT, :default_protocol => DEFAULT_PROTOCOL},
60
+ aws_access_key_id, aws_secret_access_key, params)
122
61
  end
123
62
 
124
- # TODO TRB 6/19/07 - Service gem common method
125
-
126
- # Return the +true+ if this RightS3 instance works in multi_thread state and +false+ otherwise.
127
- def multi_thread
128
- @params[:multi_thread]
129
- end
130
63
 
131
64
  #-----------------------------------------------------------------
132
65
  # Requests
@@ -172,7 +105,7 @@ module RightAws
172
105
  headers.each { |key, value| request[key.to_s] = value }
173
106
  #generate auth strings
174
107
  auth_string = canonical_string(request.method, request.path, request.to_hash)
175
- signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new("sha1"), @aws_secret_access_key, auth_string)).strip
108
+ signature = AwsUtils::sign(@aws_secret_access_key, auth_string)
176
109
  # set other headers
177
110
  request['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
178
111
  # prepare output hash
@@ -184,59 +117,10 @@ module RightAws
184
117
 
185
118
  # Sends request to Amazon and parses the response.
186
119
  # Raises AwsError if any banana happened.
187
- # TODO TRB 6/19/07:
188
- # request_info is a candidate to move to right_awsbase
189
- # because it currently appears (in identical form) in right_s3,
190
- # right_ec2, and right_sqs
191
120
  def request_info(request, parser, &block) # :nodoc:
192
121
  thread = @params[:multi_thread] ? Thread.current : Thread.main
193
122
  thread[:s3_connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError)
194
- @last_request = request[:request]
195
- @last_response = nil
196
- response=nil
197
-
198
- if(block != nil)
199
- @@bench_s3.add! do
200
- responsehdr = thread[:s3_connection].request(request) do |response|
201
- if response.is_a?(Net::HTTPSuccess)
202
- @error_handler = nil
203
- response.read_body(&block)
204
- else
205
- @error_handler = AWSErrorHandler.new(self, parser, @@amazon_problems) unless @error_handler
206
- check_result = @error_handler.check(request)
207
- if check_result
208
- @error_handler = nil
209
- return check_result
210
- end
211
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
212
- end
213
- end
214
- @@bench_xml.add! do
215
- parser.parse(responsehdr)
216
- end
217
- return parser.result
218
- end
219
- else
220
- @@bench_s3.add!{ response = thread[:s3_connection].request(request) }
221
- # check response for errors...
222
- @last_response = response
223
- if response.is_a?(Net::HTTPSuccess)
224
- @error_handler = nil
225
- @@bench_xml.add! { parser.parse(response) }
226
- return parser.result
227
- else
228
- @error_handler = AWSErrorHandler.new(self, parser, @@amazon_problems) unless @error_handler
229
- check_result = @error_handler.check(request)
230
- if check_result
231
- @error_handler = nil
232
- return check_result
233
- end
234
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
235
- end
236
- end
237
- rescue
238
- @error_handler = nil
239
- raise
123
+ request_info_impl(thread[:s3_connection], @@bench, request, parser, &block)
240
124
  end
241
125
 
242
126
 
@@ -23,7 +23,8 @@
23
23
 
24
24
  module RightAws
25
25
 
26
- class SqsInterface
26
+ class SqsInterface < RightAwsBase
27
+ include RightAwsBaseInterface
27
28
 
28
29
  SIGNATURE_VERSION = "1"
29
30
  API_VERSION = "2007-05-01"
@@ -32,45 +33,17 @@ module RightAws
32
33
  DEFAULT_PROTOCOL = 'https'
33
34
  REQUEST_TTL = 30
34
35
  DEFAULT_VISIBILITY_TIMEOUT = 30
35
- # A list of Amazon problems we can handle via AWSErrorHandler.
36
- @@amazon_problems = RightAws::AMAZON_PROBLEMS
37
-
38
- # Current aws_access_key_id
39
- attr_reader :aws_access_key_id
40
- # Last HTTP request object
41
- attr_reader :last_request
42
- # Last HTTP response object
43
- attr_reader :last_response
44
- # Last AWS errors list (used by AWSErrorHandler)
45
- attr_accessor :last_errors
46
- # Last AWS request id (used by AWSErrorHandler)
47
- attr_accessor :last_request_id
48
- # Logger object
49
- attr_accessor :logger
50
- # Initial params hash
51
- attr_accessor :params
52
-
53
- @@bench_sqs = Benchmark::Tms.new()
54
- @@bench_xml = Benchmark::Tms.new()
55
-
56
- # Benchmark::Tms instance for SQS access benchmarking.
57
- def self.bench_sqs; @@bench_sqs; end
58
- # Benchmark::Tms instance for XML parsing benchmarking.
59
- def self.bench_xml; @@bench_xml; end # For benchmark purposes.
60
36
 
61
- # Returns a list of Amazon service responses which are known to be transient problems.
62
- # We have to re-request if we get any of them, because the problem will probably disappear.
63
- # By default this method returns the same value as the AMAZON_PROBLEMS const.
64
- def self.amazon_problems
65
- @@amazon_problems
37
+
38
+ @@bench = AwsBenchmarkingBlock.new
39
+ def self.bench_xml
40
+ @@bench.xml
66
41
  end
67
-
68
- # Sets the list of Amazon side problems. Use in conjunction with the
69
- # getter to append problems.
70
- def self.amazon_problems=(problems_list)
71
- @@amazon_problems = problems_list
42
+ def self.bench_sqs
43
+ @@bench.service
72
44
  end
73
45
 
46
+
74
47
  # Creates a new SqsInterface instance.
75
48
  #
76
49
  # sqs = RightAws::SqsInterface.new('1E3GDYEOGFJPIT75KDT40','hgTHt68JY07JKUY08ftHYtERkjgtfERn57DFE379', {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> <RightSqs:0xb7af6264>
@@ -83,31 +56,10 @@ module RightAws
83
56
  # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
84
57
  #
85
58
  def initialize(aws_access_key_id, aws_secret_access_key, params={})
86
- @params = params
87
- raise AwsError.new("AWS access keys are required to operate on SQS") \
88
- if aws_access_key_id.blank? || aws_secret_access_key.blank?
89
- @aws_access_key_id = aws_access_key_id
90
- @aws_secret_access_key = aws_secret_access_key
91
- # params
92
- @params[:server] ||= DEFAULT_HOST
93
- @params[:port] ||= DEFAULT_PORT
94
- @params[:protocol] ||= DEFAULT_PROTOCOL
95
- @params[:multi_thread] ||= defined?(AWS_DAEMON)
96
- # set logger
97
- @logger = @params[:logger]
98
- @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
99
- @logger = Logger.new(STDOUT) if !@logger
100
- @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
101
- end
102
-
103
- def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
104
- AwsError::on_aws_exception(self, options)
59
+ init({:name=>'SQS', :default_host => DEFAULT_HOST, :default_port => DEFAULT_PORT, :default_protocol => DEFAULT_PROTOCOL},
60
+ aws_access_key_id, aws_secret_access_key, params)
105
61
  end
106
62
 
107
- # Return +true+ if this RightS3 instance is running in multi_thread state and +false+ otherwise.
108
- def multi_thread
109
- @params[:multi_thread]
110
- end
111
63
 
112
64
  #-----------------------------------------------------------------
113
65
  # Requests
@@ -129,7 +81,7 @@ module RightAws
129
81
  "SignatureVersion" => SIGNATURE_VERSION }
130
82
  request_hash.update(param)
131
83
  request_data = request_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
132
- request_hash['Signature'] = Base64.encode64( OpenSSL::HMAC.digest( OpenSSL::Digest::Digest.new( "sha1" ), @aws_secret_access_key, request_data)).strip
84
+ request_hash['Signature'] = AwsUtils::sign(@aws_secret_access_key, request_data)
133
85
  request_params = request_hash.to_a.collect{|key,val| key.to_s + "=" + CGI::escape(val.to_s) }.join("&")
134
86
  request = Net::HTTP::Get.new("#{queue_uri}?#{request_params}")
135
87
  # prepare output hash
@@ -156,7 +108,7 @@ module RightAws
156
108
  request['Date'] = Time.now.httpdate
157
109
  # generate authorization string
158
110
  auth_string = "#{method.upcase}\n#{request['content-md5']}\n#{request['Content-Type']}\n#{request['Date']}\n#{CGI::unescape(queue_uri)}"
159
- signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new("sha1"), @aws_secret_access_key, auth_string)).strip
111
+ signature = AwsUtils::sign(@aws_secret_access_key, auth_string)
160
112
  # set other headers
161
113
  request['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
162
114
  request['AWS-Version'] = API_VERSION
@@ -173,29 +125,7 @@ module RightAws
173
125
  def request_info(request, parser) # :nodoc:
174
126
  thread = @params[:multi_thread] ? Thread.current : Thread.main
175
127
  thread[:sqs_connection] ||= Rightscale::HttpConnection.new(:exception => AwsError)
176
- @last_request = request[:request]
177
- @last_response = nil
178
- response=nil
179
-
180
- @@bench_sqs.add!{ response = thread[:sqs_connection].request(request) }
181
- # check response for errors...
182
- @last_response = response
183
- if response.is_a?(Net::HTTPSuccess)
184
- @error_handler = nil
185
- @@bench_xml.add! { parser.parse(response) }
186
- return parser.result
187
- else
188
- @error_handler = AWSErrorHandler.new(self, parser, @@amazon_problems) unless @error_handler
189
- check_result = @error_handler.check(request)
190
- if check_result
191
- @error_handler = nil
192
- return check_result
193
- end
194
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
195
- end
196
- rescue
197
- @error_handler = nil
198
- raise
128
+ request_info_impl(thread[:sqs_connection], @@bench, request, parser)
199
129
  end
200
130
 
201
131
 
@@ -0,0 +1,85 @@
1
+ =begin
2
+ Copyright (c) 2007 RightScale, Inc.
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ 'Software'), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ =end
23
+
24
+ # Stub extension/redefinition of RightHttpConnection for testing purposes.
25
+ require 'net/http'
26
+ require 'right_http_connection'
27
+
28
+ module Net
29
+ class HTTPResponse
30
+ alias_method :real_body, :body
31
+ def setmsg(msg)
32
+ @mymsg = msg
33
+ end
34
+
35
+ def body
36
+ @mymsg ? @mymsg : real_body
37
+ end
38
+ end
39
+ end
40
+
41
+ module Rightscale
42
+
43
+ class HttpConnection
44
+ @@response_stack = []
45
+
46
+ alias_method :real_request, :request
47
+
48
+ def request(request_params, &block)
49
+ if(@@response_stack.length == 0)
50
+ return real_request(request_params, &block)
51
+ end
52
+
53
+ if(block)
54
+ # Do something special
55
+ else
56
+ next_response = HttpConnection::pop()
57
+ classname = Net::HTTPResponse::CODE_TO_OBJ["#{next_response[:code]}"]
58
+ response = classname.new("1.1", next_response[:code], next_response[:msg])
59
+ if(next_response[:msg])
60
+ response.setmsg(next_response[:msg])
61
+ end
62
+ response
63
+ end
64
+ end
65
+
66
+ def self.reset
67
+ @@response_stack = []
68
+ end
69
+
70
+ def self.push(code, msg=nil)
71
+ response = {:code => code, :msg => msg}
72
+ @@response_stack << response
73
+ end
74
+
75
+ def self.pop
76
+ @@response_stack.pop
77
+ end
78
+
79
+ def self.length
80
+ @@response_stack.length
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -25,7 +25,7 @@ class TestS3 < Test::Unit::TestCase
25
25
  end
26
26
 
27
27
  def test_03_list_empty_bucket
28
- assert_equal 0, @s3.list_bucket(@bucket).size, "#{@bucket} must exist!"
28
+ assert_equal 0, @s3.list_bucket(@bucket).size, "#{@bucket} isn't empty, arrgh!"
29
29
  end
30
30
 
31
31
  def test_04_put
@@ -44,12 +44,31 @@ class TestS3 < Test::Unit::TestCase
44
44
  def test_06_head
45
45
  assert_equal 'Woohoo1!', @s3.head(@bucket,@key1)['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'"
46
46
  end
47
+
48
+
49
+ def test_07_streaming_get
50
+ resp = String.new
51
+ assert_raise(Rightscale::AwsError) do
52
+ @s3.get(@bucket, 'undefined/key') do |chunk|
53
+ resp += chunk
54
+ end
55
+ end
56
+
57
+ resp = String.new
58
+ data1 = @s3.get(@bucket, @key1) do |chunk|
59
+ resp += chunk
60
+ end
61
+ assert_equal RIGHT_OBJECT_TEXT, resp, "Object text must be equal to '#{RIGHT_OBJECT_TEXT}'"
62
+ assert_equal @s3.get_object(@bucket, @key1), resp, "Streaming iface must return same as non-streaming"
63
+ assert_equal 'Woohoo1!', data1[:headers]['x-amz-meta-family'], "x-amz-meta-family header must be equal to 'Woohoo1!'"
64
+ end
47
65
 
48
- def test_07_delete_folder
66
+ def test_08_delete_folder
49
67
  assert_equal 1, @s3.delete_folder(@bucket, 'test').size, "Only one key(#{@key1}) must be deleted!"
50
68
  end
51
69
 
52
- def test_08_delete_bucket
70
+
71
+ def test_09_delete_bucket
53
72
  assert_raise(Rightscale::AwsError) { @s3.delete_bucket(@bucket) }
54
73
  assert @s3.clear_bucket(@bucket), 'Clear_bucket fail'
55
74
  assert_equal 0, @s3.list_bucket(@bucket).size, 'Bucket must be empty'
@@ -57,6 +76,7 @@ class TestS3 < Test::Unit::TestCase
57
76
  assert !@s3.list_all_my_buckets.map{|bucket| bucket[:name]}.include?(@bucket), "#{@bucket} must not exist"
58
77
  end
59
78
 
79
+
60
80
  #---------------------------
61
81
  # Rightscale::S3 classes
62
82
  #---------------------------
@@ -212,6 +232,16 @@ class TestS3 < Test::Unit::TestCase
212
232
  assert key1.delete
213
233
  assert !key1.exists?
214
234
  end
215
-
235
+
236
+ def test_36_set_amazon_problems
237
+ original_problems = RightAws::S3Interface.amazon_problems
238
+ assert(original_problems.length > 0)
239
+ RightAws::S3Interface.amazon_problems= original_problems << "A New Problem"
240
+ new_problems = RightAws::S3Interface.amazon_problems
241
+ assert_equal(new_problems, original_problems)
242
+
243
+ RightAws::S3Interface.amazon_problems= nil
244
+ assert_nil(RightAws::S3Interface.amazon_problems)
245
+ end
216
246
 
217
247
  end
@@ -0,0 +1,70 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestS3Stubbed < Test::Unit::TestCase
4
+
5
+ RIGHT_OBJECT_TEXT = 'Right test message'
6
+
7
+ def setup
8
+ @s3 = Rightscale::S3Interface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key)
9
+ @bucket = 'right_s3_awesome_test_bucket'
10
+ @key1 = 'test/woohoo1'
11
+ @key2 = 'test1/key/woohoo2'
12
+ @s = Rightscale::S3.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key)
13
+ Rightscale::HttpConnection.reset
14
+ end
15
+
16
+ # Non-remote tests: these use the stub version of Rightscale::HTTPConnection
17
+ def test_101_create_bucket
18
+ Rightscale::HttpConnection.push(409, 'The named bucket you tried to create already exists')
19
+ Rightscale::HttpConnection.push(500, 'We encountered an internal error. Please try again.')
20
+ Rightscale::HttpConnection.push(500, 'We encountered an internal error. Please try again.')
21
+ assert_raise RightAws::AwsError do
22
+ @s3.create_bucket(@bucket)
23
+ end
24
+ end
25
+
26
+ def test_102_list_all_my_buckets_failure
27
+ Rightscale::HttpConnection.push(401, 'Unauthorized')
28
+ assert_raise RightAws::AwsError do
29
+ @s3.list_all_my_buckets
30
+ end
31
+ end
32
+
33
+ def test_103_list_empty_bucket
34
+ Rightscale::HttpConnection.push(403, 'Access Denied')
35
+ assert_raise RightAws::AwsError do
36
+ @s3.list_bucket(@bucket)
37
+ end
38
+ end
39
+
40
+ def test_104_put
41
+ Rightscale::HttpConnection.push(400, 'Your proposed upload exceeds the maximum allowed object size.')
42
+ Rightscale::HttpConnection.push(400, 'The Content-MD5 you specified was an invalid.')
43
+ Rightscale::HttpConnection.push(409, 'Please try again')
44
+ assert_raise RightAws::AwsError do
45
+ assert @s3.put(@bucket, @key1, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo1!'), 'Put bucket fail'
46
+ end
47
+ assert_raise RightAws::AwsError do
48
+ assert @s3.put(@bucket, @key2, RIGHT_OBJECT_TEXT, 'x-amz-meta-family'=>'Woohoo2!'), 'Put bucket fail'
49
+ end
50
+ end
51
+
52
+ def test_105_get_and_get_object
53
+ Rightscale::HttpConnection.push(404, 'not found')
54
+ assert_raise(Rightscale::AwsError) { @s3.get(@bucket, 'undefined/key') }
55
+ end
56
+
57
+ def test_106_head
58
+ Rightscale::HttpConnection.push(404, 'Good Luck!')
59
+ assert_raise RightAws::AwsError do
60
+ @s3.head(@bucket,@key1)
61
+ end
62
+ end
63
+
64
+
65
+ def test_109_delete_bucket
66
+ Rightscale::HttpConnection.push(403, 'Good Luck!')
67
+ assert_raise(Rightscale::AwsError) { @s3.delete_bucket(@bucket) }
68
+ end
69
+
70
+ end
@@ -218,4 +218,21 @@ class TestSqs < Test::Unit::TestCase
218
218
  assert queue.delete(true)
219
219
  end
220
220
 
221
+ def test_27_set_amazon_problems
222
+ original_problems = RightAws::SqsInterface.amazon_problems
223
+ assert(original_problems.length > 0)
224
+ RightAws::SqsInterface.amazon_problems= original_problems << "A New Problem"
225
+ new_problems = RightAws::SqsInterface.amazon_problems
226
+ assert_equal(new_problems, original_problems)
227
+
228
+ RightAws::SqsInterface.amazon_problems= nil
229
+ assert_nil(RightAws::SqsInterface.amazon_problems)
230
+ end
231
+
232
+ def test_28_check_threading_model
233
+ assert(!@sqs.multi_thread)
234
+ newsqs = Rightscale::SqsInterface.new(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key, {:multi_thread => true})
235
+ assert(newsqs.multi_thread)
236
+ end
237
+
221
238
  end
@@ -3,8 +3,10 @@ $: << File.dirname(__FILE__)
3
3
  require 'test_credentials'
4
4
  TestCredentials.get_credentials
5
5
 
6
+ require 'http_connection'
6
7
  require 'awsbase/test_right_awsbase.rb'
7
8
  require 'ec2/test_right_ec2.rb'
8
9
  require 's3/test_right_s3.rb'
10
+ require 's3/test_right_s3_stubbed.rb'
9
11
  require 'sqs/test_right_sqs.rb'
10
12
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: right_aws
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.0
7
- date: 2007-09-12 00:00:00 -07:00
6
+ version: 1.3.0
7
+ date: 2007-09-26 00:00:00 -07:00
8
8
  summary: Interface classes for the Amazon EC2, SQS, and S3 Web Services
9
9
  require_paths:
10
10
  - lib
@@ -46,10 +46,12 @@ files:
46
46
  - test/ec2/test_right_ec2.rb
47
47
  - test/s3/test_helper.rb
48
48
  - test/s3/test_right_s3.rb
49
+ - test/s3/test_right_s3_stubbed.rb
49
50
  - test/sqs/test_helper.rb
50
51
  - test/sqs/test_right_sqs.rb
51
52
  - test/ts_right_aws.rb
52
53
  - test/test_credentials.rb
54
+ - test/http_connection.rb
53
55
  test_files:
54
56
  - test/ts_right_aws.rb
55
57
  rdoc_options:
@@ -82,5 +84,5 @@ dependencies:
82
84
  requirements:
83
85
  - - ">="
84
86
  - !ruby/object:Gem::Version
85
- version: 1.2.1
87
+ version: 1.3.0
86
88
  version: