aws 2.3.34 → 2.4.0

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