aws 2.3.34 → 2.4.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.
@@ -73,7 +73,7 @@ module Aws
73
73
  # acf.set_distribution_config(distibution[:aws_id], config) #=> true
74
74
  #
75
75
  class AcfInterface < AwsBase
76
-
76
+
77
77
  include AwsBaseInterface
78
78
 
79
79
  API_VERSION = "2010-08-01"
@@ -82,10 +82,19 @@ module Aws
82
82
  DEFAULT_PROTOCOL = 'https'
83
83
  DEFAULT_PATH = '/'
84
84
 
85
- @@bench = AwsBenchmarkingBlock.new
85
+
86
+ def self.connection_name
87
+ :acf_connection
88
+ end
89
+
90
+ @@bench = AwsBenchmarkingBlock.new
91
+ def self.bench
92
+ @@bench
93
+ end
86
94
  def self.bench_xml
87
95
  @@bench.xml
88
96
  end
97
+
89
98
  def self.bench_service
90
99
  @@bench.service
91
100
  end
@@ -104,13 +113,13 @@ module Aws
104
113
  # {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> #<Aws::AcfInterface::0xb7b3c30c>
105
114
  #
106
115
  def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
107
- init({ :name => 'ACF',
108
- :default_host => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).host : DEFAULT_HOST,
109
- :default_port => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).port : DEFAULT_PORT,
110
- :default_service => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).path : DEFAULT_PATH,
111
- :default_protocol => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).scheme : DEFAULT_PROTOCOL },
112
- aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
113
- aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
116
+ init({:name => 'ACF',
117
+ :default_host => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).host : DEFAULT_HOST,
118
+ :default_port => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).port : DEFAULT_PORT,
119
+ :default_service => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).path : DEFAULT_PATH,
120
+ :default_protocol => ENV['ACF_URL'] ? URI.parse(ENV['ACF_URL']).scheme : DEFAULT_PROTOCOL},
121
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'],
122
+ aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY'],
114
123
  params)
115
124
  end
116
125
 
@@ -119,29 +128,30 @@ module Aws
119
128
  #-----------------------------------------------------------------
120
129
 
121
130
  # Generates request hash for REST API.
122
- def generate_request(method, path, body=nil, headers={}) # :nodoc:
131
+ def generate_request(method, path, body=nil, headers={}) # :nodoc:
123
132
  headers['content-type'] ||= 'text/xml' if body
124
- headers['date'] = Time.now.httpdate
133
+ headers['date'] = Time.now.httpdate
125
134
  # Auth
126
- signature = AwsUtils::sign(@aws_secret_access_key, headers['date'])
135
+ signature = AwsUtils::sign(@aws_secret_access_key, headers['date'])
127
136
  headers['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
128
137
  # Request
129
- path = "#{@params[:default_service]}/#{API_VERSION}/#{path}"
130
- request = "Net::HTTP::#{method.capitalize}".constantize.new(path)
138
+ path = "#{@params[:default_service]}/#{API_VERSION}/#{path}"
139
+ request = "Net::HTTP::#{method.capitalize}".constantize.new(path)
131
140
  request.body = body if body
132
141
  # Set request headers
133
142
  headers.each { |key, value| request[key.to_s] = value }
134
143
  # prepare output hash
135
- { :request => request,
136
- :server => @params[:server],
137
- :port => @params[:port],
138
- :protocol => @params[:protocol] }
139
- end
140
-
141
- # Sends request to Amazon and parses the response.
142
- # Raises AwsError if any banana happened.
144
+ {:request => request,
145
+ :server => @params[:server],
146
+ :port => @params[:port],
147
+ :protocol => @params[:protocol]}
148
+ end
149
+
150
+ # Sends request to Amazon and parses the response.
151
+ # Raises AwsError if any banana happened.
152
+ # todo: remove this and switch to using request_info2
143
153
  def request_info(request, parser, options={}, &block) # :nodoc:
144
- conn = get_conn(:acf_connection, @params, @logger)
154
+ conn = get_conn(self.class.connection_name, @params, @logger)
145
155
  request_info_impl(conn, @@bench, request, parser, options, &block)
146
156
  end
147
157
 
@@ -163,13 +173,13 @@ module Aws
163
173
 
164
174
  def generate_call_reference # :nodoc:
165
175
  result = Time.now.strftime('%Y%m%d%H%M%S')
166
- 10.times{ result << rand(10).to_s }
176
+ 10.times { result << rand(10).to_s }
167
177
  result
168
178
  end
169
179
 
170
180
  def merge_headers(hash) # :nodoc:
171
181
  hash[:location] = @last_response['Location'] if @last_response['Location']
172
- hash[:e_tag] = @last_response['ETag'] if @last_response['ETag']
182
+ hash[:e_tag] = @last_response['ETag'] if @last_response['ETag']
173
183
  hash
174
184
  end
175
185
 
@@ -191,12 +201,12 @@ module Aws
191
201
  #
192
202
  def list_distributions
193
203
  request_hash = generate_request('GET', 'distribution')
194
- request_cache_or_info :list_distributions, request_hash, AcfDistributionListParser, @@bench
204
+ request_cache_or_info :list_distributions, request_hash, AcfDistributionListParser, @@bench
195
205
  end
196
206
 
197
207
  def list_streaming_distributions
198
208
  request_hash = generate_request('GET', 'streaming-distribution')
199
- request_cache_or_info :list_streaming_distributions, request_hash, AcfStreamingDistributionListParser, @@bench
209
+ request_cache_or_info :list_streaming_distributions, request_hash, AcfStreamingDistributionListParser, @@bench
200
210
  end
201
211
 
202
212
  # Create a new distribution.
@@ -215,27 +225,27 @@ module Aws
215
225
  # :caller_reference => "200809102100536497863003"}
216
226
  #
217
227
  def create_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil, default_root_object=nil)
218
- body = distribution_config_for(origin, comment, enabled, cnames, caller_reference, false, default_root_object)
228
+ body = distribution_config_for(origin, comment, enabled, cnames, caller_reference, false, default_root_object)
219
229
  request_hash = generate_request('POST', 'distribution', body.strip)
220
230
  merge_headers(request_info(request_hash, AcfDistributionParser.new))
221
231
  end
222
232
 
223
233
  def create_streaming_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil, default_root_object=nil)
224
- body = distribution_config_for(origin, comment, enabled, cnames, caller_reference, true, default_root_object)
234
+ body = distribution_config_for(origin, comment, enabled, cnames, caller_reference, true, default_root_object)
225
235
  request_hash = generate_request('POST', 'streaming-distribution', body.strip)
226
236
  merge_headers(request_info(request_hash, AcfDistributionParser.new))
227
237
  end
228
-
238
+
229
239
  def distribution_config_for(origin, comment='', enabled=true, cnames=[], caller_reference=nil, streaming = false, default_root_object=nil)
230
240
  rootElement = streaming ? "StreamingDistributionConfig" : "DistributionConfig"
231
241
  # join CNAMES
232
- cnames_str = ''
242
+ cnames_str = ''
233
243
  unless cnames.blank?
234
244
  cnames.to_a.each { |cname| cnames_str += "\n <CNAME>#{cname}</CNAME>" }
235
245
  end
236
246
  caller_reference ||= generate_call_reference
237
- root_ob = default_root_object ? "<DefaultRootObject>#{config[:default_root_object]}</DefaultRootObject>" : ""
238
- body = <<-EOXML
247
+ root_ob = default_root_object ? "<DefaultRootObject>#{config[:default_root_object]}</DefaultRootObject>" : ""
248
+ body = <<-EOXML
239
249
  <?xml version="1.0" encoding="UTF-8"?>
240
250
  <#{rootElement} xmlns=#{xmlns}>
241
251
  <Origin>#{origin}</Origin>
@@ -307,14 +317,14 @@ module Aws
307
317
  # acf.set_distribution_config('E2REJM3VUN5RSI', config) #=> true
308
318
  #
309
319
  def set_distribution_config(aws_id, config)
310
- body = distribution_config_for(config[:origin], config[:comment], config[:enabled], config[:cnames], config[:caller_reference], false)
311
- request_hash = generate_request('PUT', "distribution/#{aws_id}/config", body.strip,
320
+ body = distribution_config_for(config[:origin], config[:comment], config[:enabled], config[:cnames], config[:caller_reference], false)
321
+ request_hash = generate_request('PUT', "distribution/#{aws_id}/config", body.strip,
312
322
  'If-Match' => config[:e_tag])
313
323
  request_info(request_hash, RightHttp2xxParser.new)
314
324
  end
315
325
 
316
326
  def set_streaming_distribution_config(aws_id, config)
317
- body = distribution_config_for(config[:origin], config[:comment], config[:enabled], config[:cnames], config[:caller_reference], true)
327
+ body = distribution_config_for(config[:origin], config[:comment], config[:enabled], config[:cnames], config[:caller_reference], true)
318
328
  request_hash = generate_request('PUT', "streaming-distribution/#{aws_id}/config", body.strip,
319
329
  'If-Match' => config[:e_tag])
320
330
  request_info(request_hash, RightHttp2xxParser.new)
@@ -344,20 +354,30 @@ module Aws
344
354
  # Parses attributes common to many CF distribution API calls
345
355
  class AcfBaseDistributionParser < AwsParser # :nodoc:
346
356
  def reset
347
- @distribution = { :cnames => [] }
348
- @result = []
357
+ @distribution = {:cnames => []}
358
+ @result = []
349
359
  end
360
+
350
361
  def tagend(name)
351
362
  case name
352
- when 'Id' then @distribution[:aws_id] = @text
353
- when 'Status' then @distribution[:status] = @text
354
- when 'LastModifiedTime' then @distribution[:last_modified_time] = Time.parse(@text)
355
- when 'DomainName' then @distribution[:domain_name] = @text
356
- when 'Origin' then @distribution[:origin] = @text
357
- when 'CallerReference' then @distribution[:caller_reference] = @text
358
- when 'Comment' then @distribution[:comment] = AcfInterface::unescape(@text)
359
- when 'Enabled' then @distribution[:enabled] = @text == 'true' ? true : false
360
- when 'CNAME' then @distribution[:cnames] << @text
363
+ when 'Id' then
364
+ @distribution[:aws_id] = @text
365
+ when 'Status' then
366
+ @distribution[:status] = @text
367
+ when 'LastModifiedTime' then
368
+ @distribution[:last_modified_time] = Time.parse(@text)
369
+ when 'DomainName' then
370
+ @distribution[:domain_name] = @text
371
+ when 'Origin' then
372
+ @distribution[:origin] = @text
373
+ when 'CallerReference' then
374
+ @distribution[:caller_reference] = @text
375
+ when 'Comment' then
376
+ @distribution[:comment] = AcfInterface::unescape(@text)
377
+ when 'Enabled' then
378
+ @distribution[:enabled] = @text == 'true' ? true : false
379
+ when 'CNAME' then
380
+ @distribution[:cnames] << @text
361
381
  end
362
382
  end
363
383
  end
@@ -371,39 +391,49 @@ module Aws
371
391
 
372
392
  class AcfDistributionListParser < AcfBaseDistributionParser # :nodoc:
373
393
  def tagstart(name, attributes)
374
- @distribution = { :cnames => [] } if name == 'DistributionSummary'
394
+ @distribution = {:cnames => []} if name == 'DistributionSummary'
375
395
  end
396
+
376
397
  def tagend(name)
377
398
  super(name)
378
399
  case name
379
- when 'DistributionSummary' then @result << @distribution
400
+ when 'DistributionSummary' then
401
+ @result << @distribution
380
402
  end
381
403
  end
382
404
  end
383
405
 
384
406
  class AcfDistributionConfigParser < AwsParser # :nodoc:
385
407
  def reset
386
- @result = { :cnames => [] }
408
+ @result = {:cnames => []}
387
409
  end
410
+
388
411
  def tagend(name)
389
412
  case name
390
- when 'Origin' then @result[:origin] = @text
391
- when 'CallerReference' then @result[:caller_reference] = @text
392
- when 'Comment' then @result[:comment] = AcfInterface::unescape(@text)
393
- when 'Enabled' then @result[:enabled] = @text == 'true' ? true : false
394
- when 'CNAME' then @result[:cnames] << @text
413
+ when 'Origin' then
414
+ @result[:origin] = @text
415
+ when 'CallerReference' then
416
+ @result[:caller_reference] = @text
417
+ when 'Comment' then
418
+ @result[:comment] = AcfInterface::unescape(@text)
419
+ when 'Enabled' then
420
+ @result[:enabled] = @text == 'true' ? true : false
421
+ when 'CNAME' then
422
+ @result[:cnames] << @text
395
423
  end
396
424
  end
397
425
  end
398
426
 
399
427
  class AcfStreamingDistributionListParser < AcfBaseDistributionParser # :nodoc:
400
428
  def tagstart(name, attributes)
401
- @distribution = { :cnames => [] } if name == 'StreamingDistributionSummary'
429
+ @distribution = {:cnames => []} if name == 'StreamingDistributionSummary'
402
430
  end
431
+
403
432
  def tagend(name)
404
433
  super(name)
405
434
  case name
406
- when 'StreamingDistributionSummary' then @result << @distribution
435
+ when 'StreamingDistributionSummary' then
436
+ @result << @distribution
407
437
  end
408
438
  end
409
439
  end
data/lib/aws.rb CHANGED
@@ -15,16 +15,16 @@ require 'right_http_connection'
15
15
  $:.unshift(File.dirname(__FILE__))
16
16
  require 'awsbase/benchmark_fix'
17
17
  require 'awsbase/support'
18
- require 'awsbase/right_awsbase'
18
+ require 'awsbase/awsbase'
19
19
  require 'awsbase/aws_response_array'
20
- require 'ec2/right_ec2'
21
- require 'ec2/right_mon_interface'
22
- require 's3/right_s3_interface'
23
- require 's3/right_s3'
24
- require 'sqs/right_sqs_interface'
25
- require 'sqs/right_sqs'
26
- require 'sdb/right_sdb_interface'
27
- require 'acf/right_acf_interface'
20
+ require 'ec2/ec2'
21
+ require 'ec2/mon_interface'
22
+ require 's3/s3_interface'
23
+ require 's3/s3'
24
+ require 'sqs/sqs_interface'
25
+ require 'sqs/sqs'
26
+ require 'sdb/sdb_interface'
27
+ require 'acf/acf_interface'
28
28
  require 'elb/elb_interface'
29
29
  require 'rds/rds'
30
30
  require 'iam/iam'
@@ -1,30 +1,30 @@
1
1
  module Aws
2
2
 
3
- # This class is a special array to hold a bit of extra information about a response like:
4
- # <ResponseMetadata>
5
- # <RequestId>4f1fae46-bf3d-11de-a88b-7b5b3d23b3a7</RequestId>
6
- # </ResponseMetadata>
7
- #
8
- # Which can be accessed directly from the array using array.response_metadata
9
- #
10
- class AwsResponseArray < Array
11
-
12
- attr_accessor :response_metadata
13
-
14
- def initialize(response_metadata)
15
- @response_metadata = response_metadata
16
- end
17
-
3
+ # This class is a special array to hold a bit of extra information about a response like:
4
+ # <ResponseMetadata>
5
+ # <RequestId>4f1fae46-bf3d-11de-a88b-7b5b3d23b3a7</RequestId>
6
+ # </ResponseMetadata>
7
+ #
8
+ # Which can be accessed directly from the array using array.response_metadata
9
+ #
10
+ class AwsResponseArray < Array
11
+
12
+ attr_accessor :response_metadata
13
+
14
+ def initialize(response_metadata)
15
+ @response_metadata = response_metadata
18
16
  end
19
17
 
20
- # Used when pulling out a single response object
21
- class AwsResponseObjectHash < Hash
18
+ end
22
19
 
23
- attr_accessor :response_metadata
20
+ # Used when pulling out a single response object
21
+ class AwsResponseObjectHash < Hash
24
22
 
25
- def initialize(response_metadata)
26
- @response_metadata = response_metadata
27
- end
23
+ attr_accessor :response_metadata
28
24
 
25
+ def initialize(response_metadata)
26
+ @response_metadata = response_metadata
29
27
  end
28
+
29
+ end
30
30
  end
@@ -0,0 +1,579 @@
1
+ #
2
+ # Copyright (c) 2007-2008 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
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ # Test
25
+ module Aws
26
+ require 'digest/md5'
27
+ require 'pp'
28
+ require 'cgi'
29
+ require 'uri'
30
+ require 'xmlsimple'
31
+ require 'active_support/core_ext'
32
+
33
+ require_relative 'utils'
34
+ require_relative 'errors'
35
+ require_relative 'parsers'
36
+
37
+
38
+ class AwsBenchmarkingBlock #:nodoc:
39
+ attr_accessor :xml, :service
40
+
41
+ def initialize
42
+ # Benchmark::Tms instance for service (Ec2, S3, or SQS) access benchmarking.
43
+ @service = Benchmark::Tms.new()
44
+ # Benchmark::Tms instance for XML parsing benchmarking.
45
+ @xml = Benchmark::Tms.new()
46
+ end
47
+ end
48
+
49
+ class AwsNoChange < RuntimeError
50
+ end
51
+
52
+ class AwsBase
53
+
54
+ # Amazon HTTP Error handling
55
+
56
+ # Text, if found in an error message returned by AWS, indicates that this may be a transient
57
+ # error. Transient errors are automatically retried with exponential back-off.
58
+ AMAZON_PROBLEMS = ['internal service error',
59
+ 'is currently unavailable',
60
+ 'no response from',
61
+ 'Please try again',
62
+ 'InternalError',
63
+ 'ServiceUnavailable', #from SQS docs
64
+ 'Unavailable',
65
+ 'This application is not currently available',
66
+ 'InsufficientInstanceCapacity'
67
+ ]
68
+ @@amazon_problems = AMAZON_PROBLEMS
69
+ # Returns a list of Amazon service responses which are known to be transient problems.
70
+ # We have to re-request if we get any of them, because the problem will probably disappear.
71
+ # By default this method returns the same value as the AMAZON_PROBLEMS const.
72
+ def self.amazon_problems
73
+ @@amazon_problems
74
+ end
75
+
76
+ # Sets the list of Amazon side problems. Use in conjunction with the
77
+ # getter to append problems.
78
+ def self.amazon_problems=(problems_list)
79
+ @@amazon_problems = problems_list
80
+ end
81
+
82
+ end
83
+
84
+ module AwsBaseInterface
85
+
86
+ DEFAULT_SIGNATURE_VERSION = '2'
87
+
88
+ module ClassMethods
89
+
90
+ def self.bench
91
+ @@bench
92
+ end
93
+
94
+ def self.bench
95
+ @@bench
96
+ end
97
+
98
+ def self.bench_xml
99
+ @@bench.xml
100
+ end
101
+
102
+ def self.bench_s3
103
+ @@bench.service
104
+ end
105
+ end
106
+
107
+ @@caching = false
108
+
109
+ def self.caching
110
+ @@caching
111
+ end
112
+
113
+ def self.caching=(caching)
114
+ @@caching = caching
115
+ end
116
+
117
+ # Current aws_access_key_id
118
+ attr_reader :aws_access_key_id
119
+ # Last HTTP request object
120
+ attr_reader :last_request
121
+ # Last HTTP response object
122
+ attr_reader :last_response
123
+ # Last AWS errors list (used by AWSErrorHandler)
124
+ attr_accessor :last_errors
125
+ # Last AWS request id (used by AWSErrorHandler)
126
+ attr_accessor :last_request_id
127
+ # Logger object
128
+ attr_accessor :logger
129
+ # Initial params hash
130
+ attr_accessor :params
131
+ # RightHttpConnection instance
132
+ # there's a method now to get this since it could be per thread or what have you
133
+ # attr_reader :connection
134
+ # Cache
135
+ attr_reader :cache
136
+ # Signature version (all services except s3)
137
+ attr_reader :signature_version
138
+
139
+ def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
140
+ @params = params
141
+ raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
142
+ if aws_access_key_id.blank? || aws_secret_access_key.blank?
143
+ @aws_access_key_id = aws_access_key_id
144
+ @aws_secret_access_key = aws_secret_access_key
145
+ # if the endpoint was explicitly defined - then use it
146
+ if @params[:endpoint_url]
147
+ @params[:server] = URI.parse(@params[:endpoint_url]).host
148
+ @params[:port] = URI.parse(@params[:endpoint_url]).port
149
+ @params[:service] = URI.parse(@params[:endpoint_url]).path
150
+ @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
151
+ @params[:region] = nil
152
+ else
153
+ @params[:server] ||= service_info[:default_host]
154
+ @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
155
+ @params[:port] ||= service_info[:default_port]
156
+ @params[:service] ||= service_info[:default_service]
157
+ @params[:protocol] ||= service_info[:default_protocol]
158
+ @params[:api_version] ||= service_info[:api_version]
159
+ end
160
+ if !@params[:multi_thread].nil? && @params[:connection_mode].nil? # user defined this
161
+ @params[:connection_mode] = @params[:multi_thread] ? :per_thread : :single
162
+ end
163
+ # @params[:multi_thread] ||= defined?(AWS_DAEMON)
164
+ @params[:connection_mode] ||= :default
165
+ @params[:connection_mode] = :per_request if @params[:connection_mode] == :default
166
+ @logger = @params[:logger]
167
+ @logger = Rails.logger if !@logger && defined?(Rails) && defined?(Rails.logger)
168
+ @logger = ::Rails.logger if !@logger && defined?(::Rails.logger)
169
+ @logger = Logger.new(STDOUT) if !@logger
170
+ @logger.info "New #{self.class.name} using #{@params[:connection_mode].to_s}-connection mode"
171
+ @error_handler = nil
172
+ @cache = {}
173
+ @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
174
+ end
175
+
176
+ def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil)
177
+ case signature_version.to_s
178
+ when '0' then
179
+ AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
180
+ when '1' then
181
+ AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
182
+ when '2' then
183
+ AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
184
+ else
185
+ raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
186
+ end
187
+ end
188
+
189
+ def generate_request(action, params={})
190
+ generate_request2(@aws_access_key_id, @aws_secret_access_key, action, @params[:api_version], @params, params)
191
+ end
192
+
193
+ # FROM SDB
194
+ def generate_request2(aws_access_key, aws_secret_key, action, api_version, lib_params, user_params={}, options={}) #:nodoc:
195
+ # remove empty params from request
196
+ user_params.delete_if { |key, value| value.nil? }
197
+ # user_params.each_pair do |k,v|
198
+ # user_params[k] = v.force_encoding("UTF-8")
199
+ # end
200
+ #params_string = params.to_a.collect{|key,val| key + "=#{CGI::escape(val.to_s)}" }.join("&")
201
+ # prepare service data
202
+ service = lib_params[:service]
203
+ # puts 'service=' + service.to_s
204
+ service_hash = {"Action" => action,
205
+ "AWSAccessKeyId" => aws_access_key}
206
+ service_hash.update("Version" => api_version) if api_version
207
+ service_hash.update(user_params)
208
+ service_params = signed_service_params(aws_secret_key, service_hash, :get, lib_params[:server], lib_params[:service])
209
+ #
210
+ # use POST method if the length of the query string is too large
211
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
212
+ if service_params.size > 2000
213
+ if signature_version == '2'
214
+ # resign the request because HTTP verb is included into signature
215
+ service_params = signed_service_params(aws_secret_key, service_hash, :post, lib_params[:server], service)
216
+ end
217
+ request = Net::HTTP::Post.new(service)
218
+ request.body = service_params
219
+ request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
220
+ else
221
+ request = Net::HTTP::Get.new("#{service}?#{service_params}")
222
+ end
223
+
224
+ #puts "\n\n --------------- QUERY REQUEST TO AWS -------------- \n\n"
225
+ #puts "#{@params[:service]}?#{service_params}\n\n"
226
+
227
+ # prepare output hash
228
+ {:request => request,
229
+ :server => lib_params[:server],
230
+ :port => lib_params[:port],
231
+ :protocol => lib_params[:protocol]}
232
+ end
233
+
234
+ def get_conn(connection_name, lib_params, logger)
235
+ # thread = lib_params[:multi_thread] ? Thread.current : Thread.main
236
+ # thread[connection_name] ||= Rightscale::HttpConnection.new(:exception => Aws::AwsError, :logger => logger)
237
+ # conn = thread[connection_name]
238
+ # return conn
239
+ http_conn = nil
240
+ conn_mode = lib_params[:connection_mode]
241
+
242
+ # Slice all parameters accepted by Rightscale::HttpConnection#new
243
+ params = lib_params.slice(
244
+ :user_agent, :ca_file, :http_connection_retry_count, :http_connection_open_timeout,
245
+ :http_connection_read_timeout, :http_connection_retry_delay
246
+ )
247
+ params.merge!(:exception => AwsError, :logger => logger)
248
+
249
+ if conn_mode == :per_request
250
+ http_conn = Rightscale::HttpConnection.new(params)
251
+
252
+ elsif conn_mode == :per_thread || conn_mode == :single
253
+ thread = conn_mode == :per_thread ? Thread.current : Thread.main
254
+ thread[connection_name] ||= Rightscale::HttpConnection.new(params)
255
+ http_conn = thread[connection_name]
256
+ # ret = request_info_impl(http_conn, bench, request, parser, &block)
257
+ end
258
+ return http_conn
259
+
260
+ end
261
+
262
+ def close_conn(conn_name)
263
+ conn_mode = @params[:connection_mode]
264
+ if conn_mode == :per_thread || conn_mode == :single
265
+ thread = conn_mode == :per_thread ? Thread.current : Thread.main
266
+ if !thread[conn_name].nil?
267
+ thread[conn_name].finish
268
+ thread[conn_name] = nil
269
+ end
270
+ end
271
+ end
272
+
273
+ def connection
274
+ get_conn(self.class.connection_name, self.params, self.logger)
275
+ end
276
+
277
+ def close_connection
278
+ close_conn(self.class.connection_name)
279
+ end
280
+
281
+
282
+ def request_info2(request, parser, lib_params, connection_name, logger, bench, options={}, &block) #:nodoc:
283
+ ret = nil
284
+ # puts 'OPTIONS=' + options.inspect
285
+ http_conn = get_conn(connection_name, lib_params, logger)
286
+ begin
287
+ # todo: this QueryTimeout retry should go into a SimpleDbErrorHandler, not here
288
+ retry_count = 1
289
+ count = 0
290
+ while count <= retry_count
291
+ puts 'RETRYING QUERY due to QueryTimeout...' if count > 0
292
+ begin
293
+ ret = request_info_impl(http_conn, bench, request, parser, options, &block)
294
+ break
295
+ rescue Aws::AwsError => ex
296
+ if !ex.include?(/QueryTimeout/) || count == retry_count
297
+ raise ex
298
+ end
299
+ end
300
+ count += 1
301
+ end
302
+ ensure
303
+ http_conn.finish if http_conn && lib_params[:connection_mode] == :per_request
304
+ end
305
+ ret
306
+ end
307
+
308
+ # This is the latest and greatest now. Service must have connection_name defined.
309
+ def request_info3(service_interface, request, parser, options, &block)
310
+ request_info2(request, parser,
311
+ service_interface.params,
312
+ service_interface.class.connection_name,
313
+ service_interface.logger,
314
+ service_interface.class.bench,
315
+ options, &block)
316
+ end
317
+
318
+
319
+ # This is the direction we should head instead of writing our own parsers for everything, much simpler
320
+ # params:
321
+ # - :group_tags => hash of indirection to eliminate, see: http://xml-simple.rubyforge.org/
322
+ # - :force_array => true for all or an array of tag names to force
323
+ # - :pull_out_array => an array of levels to dig into when generating return value (see rds.rb for example)
324
+ def request_info_xml_simple(connection_name, lib_params, request, logger, params = {})
325
+
326
+ @connection = get_conn(connection_name, lib_params, logger)
327
+ begin
328
+ @last_request = request[:request]
329
+ @last_response = nil
330
+
331
+ response = @connection.request(request)
332
+ # puts "response=" + response.body
333
+ # benchblock.service.add!{ response = @connection.request(request) }
334
+ # check response for errors...
335
+ @last_response = response
336
+ if response.is_a?(Net::HTTPSuccess)
337
+ @error_handler = nil
338
+ # benchblock.xml.add! { parser.parse(response) }
339
+ # return parser.result
340
+ force_array = params[:force_array] || false
341
+ # Force_array and group_tags don't work nice together so going to force array manually
342
+ xml_simple_options = {"KeyToSymbol"=>false, 'ForceArray' => false}
343
+ xml_simple_options["GroupTags"] = params[:group_tags] if params[:group_tags]
344
+
345
+ # { 'GroupTags' => { 'searchpath' => 'dir' }
346
+ # 'ForceArray' => %r(_list$)
347
+ parsed = XmlSimple.xml_in(response.body, xml_simple_options)
348
+ # todo: we may want to consider stripping off a couple of layers when doing this, for instance:
349
+ # <DescribeDBInstancesResponse xmlns="http://rds.amazonaws.com/admin/2009-10-16/">
350
+ # <DescribeDBInstancesResult>
351
+ # <DBInstances>
352
+ # <DBInstance>....
353
+ # Strip it off and only return an array or hash of <DBInstance>'s (hash by identifier).
354
+ # would have to be able to make the RequestId available somehow though, perhaps some special array subclass which included that?
355
+ unless force_array.is_a? Array
356
+ force_array = []
357
+ end
358
+ parsed = symbolize(parsed, force_array)
359
+ # puts 'parsed=' + parsed.inspect
360
+ if params[:pull_out_array]
361
+ ret = Aws::AwsResponseArray.new(parsed[:response_metadata])
362
+ level_hash = parsed
363
+ params[:pull_out_array].each do |x|
364
+ level_hash = level_hash[x]
365
+ end
366
+ if level_hash.is_a? Hash # When there's only one
367
+ ret << level_hash
368
+ else # should be array
369
+ # puts 'level_hash=' + level_hash.inspect
370
+ level_hash.each do |x|
371
+ ret << x
372
+ end
373
+ end
374
+ elsif params[:pull_out_single]
375
+ # returns a single object
376
+ ret = AwsResponseObjectHash.new(parsed[:response_metadata])
377
+ level_hash = parsed
378
+ params[:pull_out_single].each do |x|
379
+ level_hash = level_hash[x]
380
+ end
381
+ ret.merge!(level_hash)
382
+ else
383
+ ret = parsed
384
+ end
385
+ return ret
386
+
387
+ else
388
+ @error_handler = AWSErrorHandler.new(self, nil, :errors_list => self.class.amazon_problems) unless @error_handler
389
+ check_result = @error_handler.check(request)
390
+ if check_result
391
+ @error_handler = nil
392
+ return check_result
393
+ end
394
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
395
+ raise AwsError2.new(@last_response.code, @last_request_id, request_text_data, @last_response.body)
396
+ end
397
+ ensure
398
+ @connection.finish if @connection && lib_params[:connection_mode] == :per_request
399
+ end
400
+
401
+ end
402
+
403
+ def symbolize(hash, force_array)
404
+ ret = {}
405
+ hash.keys.each do |key|
406
+ val = hash[key]
407
+ if val.is_a? Hash
408
+ val = symbolize(val, force_array)
409
+ if force_array.include? key
410
+ val = [val]
411
+ end
412
+ elsif val.is_a? Array
413
+ val = val.collect { |x| symbolize(x, force_array) }
414
+ end
415
+ ret[key.underscore.to_sym] = val
416
+ end
417
+ ret
418
+ end
419
+
420
+ # Returns +true+ if the describe_xxx responses are being cached
421
+ def caching?
422
+ @params.key?(:cache) ? @params[:cache] : @@caching
423
+ end
424
+
425
+ # Check if the aws function response hits the cache or not.
426
+ # If the cache hits:
427
+ # - raises an +AwsNoChange+ exception if +do_raise+ == +:raise+.
428
+ # - returnes parsed response from the cache if it exists or +true+ otherwise.
429
+ # If the cache miss or the caching is off then returns +false+.
430
+ def cache_hits?(function, response, do_raise=:raise)
431
+ result = false
432
+ if caching?
433
+ function = function.to_sym
434
+ # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
435
+ response = response.sub(%r{<requestId>.+?</requestId>}, '')
436
+ response_md5 =Digest::MD5.hexdigest(response).to_s
437
+ # check for changes
438
+ unless @cache[function] && @cache[function][:response_md5] == response_md5
439
+ # well, the response is new, reset cache data
440
+ update_cache(function, {:response_md5 => response_md5,
441
+ :timestamp => Time.now,
442
+ :hits => 0,
443
+ :parsed => nil})
444
+ else
445
+ # aha, cache hits, update the data and throw an exception if needed
446
+ @cache[function][:hits] += 1
447
+ if do_raise == :raise
448
+ raise(AwsNoChange, "Cache hit: #{function} response has not changed since "+
449
+ "#{@cache[function][:timestamp].strftime('%Y-%m-%d %H:%M:%S')}, "+
450
+ "hits: #{@cache[function][:hits]}.")
451
+ else
452
+ result = @cache[function][:parsed] || true
453
+ end
454
+ end
455
+ end
456
+ result
457
+ end
458
+
459
+ def update_cache(function, hash)
460
+ (@cache[function.to_sym] ||= {}).merge!(hash) if caching?
461
+ end
462
+
463
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
464
+ raise if $!.is_a?(AwsNoChange)
465
+ AwsError::on_aws_exception(self, options)
466
+ end
467
+
468
+ # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
469
+ def multi_thread
470
+ @params[:multi_thread]
471
+ end
472
+
473
+
474
+ def request_info_impl(connection, benchblock, request, parser, options={}, &block) #:nodoc:
475
+ @connection = connection
476
+ @last_request = request[:request]
477
+ @last_response = nil
478
+ response =nil
479
+ blockexception = nil
480
+
481
+ # puts 'OPTIONS2=' + options.inspect
482
+
483
+ if (block != nil)
484
+ # TRB 9/17/07 Careful - because we are passing in blocks, we get a situation where
485
+ # an exception may get thrown in the block body (which is high-level
486
+ # code either here or in the application) but gets caught in the
487
+ # low-level code of HttpConnection. The solution is not to let any
488
+ # exception escape the block that we pass to HttpConnection::request.
489
+ # Exceptions can originate from code directly in the block, or from user
490
+ # code called in the other block which is passed to response.read_body.
491
+ benchblock.service.add! do
492
+ responsehdr = @connection.request(request) do |response|
493
+ #########
494
+ begin
495
+ @last_response = response
496
+ if response.is_a?(Net::HTTPSuccess)
497
+ @error_handler = nil
498
+ response.read_body(&block)
499
+ else
500
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
501
+ check_result = @error_handler.check(request, options)
502
+ if check_result
503
+ @error_handler = nil
504
+ return check_result
505
+ end
506
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
507
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
508
+ end
509
+ rescue Exception => e
510
+ blockexception = e
511
+ end
512
+ end
513
+ #########
514
+
515
+ #OK, now we are out of the block passed to the lower level
516
+ if (blockexception)
517
+ raise blockexception
518
+ end
519
+ benchblock.xml.add! do
520
+ parser.parse(responsehdr)
521
+ end
522
+ return parser.result
523
+ end
524
+ else
525
+ benchblock.service.add! { response = @connection.request(request) }
526
+ # check response for errors...
527
+ @last_response = response
528
+ if response.is_a?(Net::HTTPSuccess)
529
+ @error_handler = nil
530
+ benchblock.xml.add! { parser.parse(response) }
531
+ return parser.result
532
+ else
533
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
534
+ check_result = @error_handler.check(request, options)
535
+ if check_result
536
+ @error_handler = nil
537
+ return check_result
538
+ end
539
+ request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
540
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id, request_text_data)
541
+ end
542
+ end
543
+ rescue
544
+ @error_handler = nil
545
+ raise
546
+ end
547
+
548
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
549
+ # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
550
+ # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
551
+ # If the caching is enabled and hit then throw AwsNoChange.
552
+ # P.S. caching works for the whole images list only! (when the list param is blank)
553
+ # check cache
554
+ response, params = request_info(link, RightDummyParser.new)
555
+ cache_hits?(method.to_sym, response.body) if use_cache
556
+ parser = parser_class.new(:logger => @logger)
557
+ benchblock.xml.add! { parser.parse(response, params) }
558
+ result = block_given? ? yield(parser) : parser.result
559
+ # update parsed data
560
+ update_cache(method.to_sym, :parsed => result) if use_cache
561
+ result
562
+ end
563
+
564
+ # Returns Amazons request ID for the latest request
565
+ def last_request_id
566
+ @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}] && $1
567
+ end
568
+
569
+ def hash_params(prefix, list) #:nodoc:
570
+ groups = {}
571
+ list.each_index { |i| groups.update("#{prefix}.#{i+1}"=>list[i]) } if list
572
+ return groups
573
+ end
574
+
575
+ end
576
+
577
+
578
+ end
579
+