right_aws 1.10.0 → 2.0.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.
Files changed (39) hide show
  1. data/History.txt +53 -15
  2. data/Manifest.txt +16 -0
  3. data/README.txt +10 -9
  4. data/Rakefile +13 -15
  5. data/lib/acf/right_acf_interface.rb +224 -118
  6. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  7. data/lib/acf/right_acf_streaming_interface.rb +236 -0
  8. data/lib/acw/right_acw_interface.rb +249 -0
  9. data/lib/as/right_as_interface.rb +699 -0
  10. data/lib/awsbase/right_awsbase.rb +232 -51
  11. data/lib/awsbase/support.rb +4 -0
  12. data/lib/ec2/right_ec2.rb +33 -1375
  13. data/lib/ec2/right_ec2_ebs.rb +452 -0
  14. data/lib/ec2/right_ec2_images.rb +373 -0
  15. data/lib/ec2/right_ec2_instances.rb +755 -0
  16. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  17. data/lib/ec2/right_ec2_reserved_instances.rb +170 -0
  18. data/lib/ec2/right_ec2_security_groups.rb +280 -0
  19. data/lib/ec2/right_ec2_spot_instances.rb +399 -0
  20. data/lib/ec2/right_ec2_vpc.rb +571 -0
  21. data/lib/elb/right_elb_interface.rb +496 -0
  22. data/lib/rds/right_rds_interface.rb +998 -0
  23. data/lib/right_aws.rb +18 -4
  24. data/lib/s3/right_s3.rb +39 -7
  25. data/lib/s3/right_s3_interface.rb +77 -53
  26. data/lib/sdb/active_sdb.rb +203 -11
  27. data/lib/sdb/right_sdb_interface.rb +68 -45
  28. data/lib/sqs/right_sqs_gen2.rb +73 -16
  29. data/lib/sqs/right_sqs_gen2_interface.rb +131 -51
  30. data/lib/sqs/right_sqs_interface.rb +2 -4
  31. data/test/acf/test_right_acf.rb +10 -18
  32. data/test/rds/test_helper.rb +2 -0
  33. data/test/rds/test_right_rds.rb +120 -0
  34. data/test/s3/test_right_s3.rb +10 -8
  35. data/test/s3/test_right_s3_stubbed.rb +6 -4
  36. data/test/sdb/test_active_sdb.rb +70 -12
  37. data/test/sdb/test_right_sdb.rb +13 -7
  38. data/test/sqs/test_right_sqs_gen2.rb +104 -49
  39. metadata +103 -14
@@ -23,7 +23,8 @@
23
23
 
24
24
  # Test
25
25
  module RightAws
26
- require 'md5'
26
+ # require 'md5'
27
+ require 'digest/md5'
27
28
  require 'pp'
28
29
 
29
30
  class AwsUtils #:nodoc:
@@ -32,6 +33,13 @@ module RightAws
32
33
  if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
33
34
  @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
34
35
  end
36
+
37
+ def self.utc_iso8601(time)
38
+ if time.is_a?(Fixnum) then time = Time::at(time)
39
+ elsif time.is_a?(String) then time = Time::parse(time)
40
+ end
41
+ time.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
42
+ end
35
43
 
36
44
  def self.sign(aws_secret_access_key, auth_string)
37
45
  Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
@@ -47,7 +55,7 @@ module RightAws
47
55
 
48
56
  # Set a timestamp and a signature version
49
57
  def self.fix_service_params(service_hash, signature)
50
- service_hash["Timestamp"] ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
58
+ service_hash["Timestamp"] ||= utc_iso8601(Time.now) unless service_hash["Expires"]
51
59
  service_hash["SignatureVersion"] = signature
52
60
  service_hash
53
61
  end
@@ -123,6 +131,11 @@ module RightAws
123
131
  $1
124
132
  end
125
133
 
134
+ def self.split_items_and_params(array)
135
+ items = Array(array).flatten.compact
136
+ params = items.last.kind_of?(Hash) ? items.pop : {}
137
+ [items, params]
138
+ end
126
139
  end
127
140
 
128
141
  class AwsBenchmarkingBlock #:nodoc:
@@ -145,15 +158,16 @@ module RightAws
145
158
  # Text, if found in an error message returned by AWS, indicates that this may be a transient
146
159
  # error. Transient errors are automatically retried with exponential back-off.
147
160
  AMAZON_PROBLEMS = [ 'internal service error',
148
- 'is currently unavailable',
149
- 'no response from',
150
- 'Please try again',
151
- 'InternalError',
152
- 'ServiceUnavailable', #from SQS docs
153
- 'Unavailable',
154
- 'This application is not currently available',
155
- 'InsufficientInstanceCapacity'
156
- ]
161
+ 'is currently unavailable',
162
+ 'no response from',
163
+ 'Please try again',
164
+ 'InternalError',
165
+ 'Internal Server Error',
166
+ 'ServiceUnavailable', #from SQS docs
167
+ 'Unavailable',
168
+ 'This application is not currently available',
169
+ 'InsufficientInstanceCapacity'
170
+ ]
157
171
  @@amazon_problems = AMAZON_PROBLEMS
158
172
  # Returns a list of Amazon service responses which are known to be transient problems.
159
173
  # We have to re-request if we get any of them, because the problem will probably disappear.
@@ -183,6 +197,8 @@ module RightAws
183
197
 
184
198
  # Current aws_access_key_id
185
199
  attr_reader :aws_access_key_id
200
+ # Current aws_secret_access_key
201
+ attr_reader :aws_secret_access_key
186
202
  # Last HTTP request object
187
203
  attr_reader :last_request
188
204
  # Last HTTP response object
@@ -204,6 +220,9 @@ module RightAws
204
220
 
205
221
  def init(service_info, aws_access_key_id, aws_secret_access_key, params={}) #:nodoc:
206
222
  @params = params
223
+ # If one defines EC2_URL he may forget to use a single slash as an "empty service" path.
224
+ # Amazon does not like this therefore add this bad boy if he is missing...
225
+ service_info[:default_service] = '/' if service_info[:default_service].blank?
207
226
  raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
208
227
  if aws_access_key_id.blank? || aws_secret_access_key.blank?
209
228
  @aws_access_key_id = aws_access_key_id
@@ -213,6 +232,8 @@ module RightAws
213
232
  @params[:server] = URI.parse(@params[:endpoint_url]).host
214
233
  @params[:port] = URI.parse(@params[:endpoint_url]).port
215
234
  @params[:service] = URI.parse(@params[:endpoint_url]).path
235
+ # make sure the 'service' path is not empty
236
+ @params[:service] = service_info[:default_service] if @params[:service].blank?
216
237
  @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
217
238
  @params[:region] = nil
218
239
  else
@@ -222,11 +243,15 @@ module RightAws
222
243
  @params[:service] ||= service_info[:default_service]
223
244
  @params[:protocol] ||= service_info[:default_protocol]
224
245
  end
225
- @params[:multi_thread] ||= defined?(AWS_DAEMON)
246
+ # @params[:multi_thread] ||= defined?(AWS_DAEMON)
247
+ @params[:connections] ||= :shared # || :dedicated
248
+ @params[:max_connections] ||= 10
249
+ @params[:connection_lifetime] ||= 20*60
250
+ @params[:api_version] ||= service_info[:default_api_version]
226
251
  @logger = @params[:logger]
227
252
  @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
228
253
  @logger = Logger.new(STDOUT) if !@logger
229
- @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
254
+ @logger.info "New #{self.class.name} using #{@params[:connections]} connections mode"
230
255
  @error_handler = nil
231
256
  @cache = {}
232
257
  @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
@@ -256,7 +281,8 @@ module RightAws
256
281
  if caching?
257
282
  function = function.to_sym
258
283
  # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
259
- response = response.sub(%r{<requestId>.+?</requestId>}, '')
284
+ # feb 04, 2009 (load balancer uses 'RequestId' hence use 'i' modifier to hit it also)
285
+ response = response.sub(%r{<requestId>.+?</requestId>}i, '')
260
286
  response_md5 = MD5.md5(response).to_s
261
287
  # check for changes
262
288
  unless @cache[function] && @cache[function][:response_md5] == response_md5
@@ -289,16 +315,85 @@ module RightAws
289
315
  AwsError::on_aws_exception(self, options)
290
316
  end
291
317
 
292
- # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
293
- def multi_thread
294
- @params[:multi_thread]
318
+ # # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
319
+ # def multi_thread
320
+ # @params[:multi_thread]
321
+ # end
322
+
323
+ # ACF, AMS, EC2, LBS and SDB uses this guy
324
+ # SQS and S3 use their own methods
325
+ def generate_request_impl(verb, action, options={}) #:nodoc:
326
+ # Form a valid http verb: 'GET' or 'POST' (all the other are not supported now)
327
+ http_verb = verb.to_s.upcase
328
+ # remove empty keys from request options
329
+ options.delete_if { |key, value| value.nil? }
330
+ # prepare service data
331
+ service_hash = {"Action" => action,
332
+ "AWSAccessKeyId" => @aws_access_key_id,
333
+ "Version" => @params[:api_version] }
334
+ service_hash.merge!(options)
335
+ # Sign request options
336
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:server], @params[:service])
337
+ # Use POST if the length of the query string is too large
338
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
339
+ if http_verb != 'POST' && service_params.size > 2000
340
+ http_verb = 'POST'
341
+ if signature_version == '2'
342
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:server], @params[:service])
343
+ end
344
+ end
345
+ # create a request
346
+ case http_verb
347
+ when 'GET'
348
+ request = Net::HTTP::Get.new("#{@params[:service]}?#{service_params}")
349
+ when 'POST'
350
+ request = Net::HTTP::Post.new(@params[:service])
351
+ request.body = service_params
352
+ request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
353
+ else
354
+ raise "Unsupported HTTP verb #{verb.inspect}!"
355
+ end
356
+ # prepare output hash
357
+ { :request => request,
358
+ :server => @params[:server],
359
+ :port => @params[:port],
360
+ :protocol => @params[:protocol] }
361
+ end
362
+
363
+ def get_connection(aws_service, request) #:nodoc
364
+ server_url = "#{request[:protocol]}://#{request[:server]}:#{request[:port]}}"
365
+ #
366
+ case @params[:connections].to_s
367
+ when 'dedicated'
368
+ @connections_storage ||= {}
369
+ else # 'dedicated'
370
+ @connections_storage = (Thread.current[aws_service] ||= {})
371
+ end
372
+ #
373
+ @connections_storage[server_url] ||= {}
374
+ @connections_storage[server_url][:last_used_at] = Time.now
375
+ @connections_storage[server_url][:connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
376
+ # keep X most recent connections (but were used not far than Y minutes ago)
377
+ connections = 0
378
+ @connections_storage.to_a.sort{|i1, i2| i2[1][:last_used_at] <=> i1[1][:last_used_at]}.to_a.each do |i|
379
+ if i[0] != server_url && (@params[:max_connections] <= connections || i[1][:last_used_at] < Time.now - @params[:connection_lifetime])
380
+ # delete the connection from the list
381
+ @connections_storage.delete(i[0])
382
+ # then finish it
383
+ i[1][:connection].finish((@params[:max_connections] <= connections) ? "out-of-limit" : "out-of-date") rescue nil
384
+ else
385
+ connections += 1
386
+ end
387
+ end
388
+ @connections_storage[server_url][:connection]
295
389
  end
296
390
 
297
- def request_info_impl(connection, benchblock, request, parser, &block) #:nodoc:
298
- @connection = connection
391
+ # All services uses this guy.
392
+ def request_info_impl(aws_service, benchblock, request, parser, &block) #:nodoc:
393
+ @connection = get_connection(aws_service, request)
299
394
  @last_request = request[:request]
300
395
  @last_response = nil
301
- response=nil
396
+ response = nil
302
397
  blockexception = nil
303
398
 
304
399
  if(block != nil)
@@ -382,7 +477,73 @@ module RightAws
382
477
 
383
478
  # Returns Amazons request ID for the latest request
384
479
  def last_request_id
385
- @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}] && $1
480
+ @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}i] && $1
481
+ end
482
+
483
+ # Format array of items into Amazons handy hash ('?' is a place holder):
484
+ #
485
+ # amazonize_list('Item', ['a', 'b', 'c']) =>
486
+ # { 'Item.1' => 'a', 'Item.2' => 'b', 'Item.3' => 'c' }
487
+ #
488
+ # amazonize_list('Item.?.instance', ['a', 'c']) #=>
489
+ # { 'Item.1.instance' => 'a', 'Item.2.instance' => 'c' }
490
+ #
491
+ # amazonize_list(['Item.?.Name', 'Item.?.Value'], {'A' => 'a', 'B' => 'b'}) #=>
492
+ # { 'Item.1.Name' => 'A', 'Item.1.Value' => 'a',
493
+ # 'Item.2.Name' => 'B', 'Item.2.Value' => 'b' }
494
+ #
495
+ # amazonize_list(['Item.?.Name', 'Item.?.Value'], [['A','a'], ['B','b']]) #=>
496
+ # { 'Item.1.Name' => 'A', 'Item.1.Value' => 'a',
497
+ # 'Item.2.Name' => 'B', 'Item.2.Value' => 'b' }
498
+ #
499
+ # amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], {'A' => ['aa','ab'], 'B' => ['ba','bb']}) #=>
500
+ # amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], [['A',['aa','ab']], ['B',['ba','bb']]]) #=>
501
+ # {"Filter.1.Key"=>"A",
502
+ # "Filter.1.Value.1"=>"aa",
503
+ # "Filter.1.Value.2"=>"ab",
504
+ # "Filter.2.Key"=>"B",
505
+ # "Filter.2.Value.1"=>"ba",
506
+ # "Filter.2.Value.2"=>"bb"}
507
+ def amazonize_list(masks, list) #:nodoc:
508
+ groups = {}
509
+ Array(list).each_with_index do |list_item, i|
510
+ Array(masks).each_with_index do |mask, mask_idx|
511
+ key = mask[/\?/] ? mask.dup : mask.dup + '.?'
512
+ key.sub!('?', (i+1).to_s)
513
+ value = Array(list_item)[mask_idx]
514
+ if value.is_a?(Array)
515
+ groups.merge!(amazonize_list(key, value))
516
+ else
517
+ groups[key] = value
518
+ end
519
+ end
520
+ end
521
+ groups
522
+ end
523
+
524
+ BLOCK_DEVICE_KEY_MAPPING = { # :nodoc:
525
+ :device_name => 'DeviceName',
526
+ :virtual_name => 'VirtualName',
527
+ :no_device => 'NoDevice',
528
+ :ebs_snapshot_id => 'Ebs.SnapshotId',
529
+ :ebs_volume_size => 'Ebs.VolumeSize',
530
+ :ebs_delete_on_termination => 'Ebs.DeleteOnTermination' }
531
+
532
+ def amazonize_block_device_mappings(block_device_mappings, key = 'BlockDeviceMapping') # :nodoc:
533
+ result = {}
534
+ unless block_device_mappings.blank?
535
+ block_device_mappings = [block_device_mappings] unless block_device_mappings.is_a?(Array)
536
+ block_device_mappings.each_with_index do |b, idx|
537
+ BLOCK_DEVICE_KEY_MAPPING.each do |local_name, remote_name|
538
+ value = b[local_name]
539
+ case local_name
540
+ when :no_device then value = value ? '' : nil # allow to pass :no_device as boolean
541
+ end
542
+ result["#{key}.#{idx+1}.#{remote_name}"] = value unless value.nil?
543
+ end
544
+ end
545
+ end
546
+ result
386
547
  end
387
548
 
388
549
  end
@@ -518,7 +679,7 @@ module RightAws
518
679
  last_errors_text = ''
519
680
  response = @aws.last_response
520
681
  # log error
521
- request_text_data = "#{request[:server]}:#{request[:port]}#{request[:request].path}"
682
+ request_text_data = "#{request[:protocol]}://#{request[:server]}:#{request[:port]}#{request[:request].path}"
522
683
  # is this a redirect?
523
684
  # yes!
524
685
  if response.is_a?(Net::HTTPRedirection)
@@ -528,33 +689,36 @@ module RightAws
528
689
  @aws.logger.warn("##### #{@aws.class.name} returned an error: #{response.code} #{response.message}\n#{response.body} #####")
529
690
  @aws.logger.warn("##### #{@aws.class.name} request: #{request_text_data} ####")
530
691
  end
531
- # Check response body: if it is an Amazon XML document or not:
532
- if redirect_detected || (response.body && response.body[/<\?xml/]) # ... it is a xml document
692
+
693
+ # Extract error/redirection message from the response body
694
+ # Amazon claims that a redirection must have a body but somethimes it is nil....
695
+ if response.body && response.body[/^(<\?xml|<ErrorResponse)/]
696
+ error_parser = RightErrorResponseParser.new
533
697
  @aws.class.bench_xml.add! do
534
- error_parser = RightErrorResponseParser.new
535
- error_parser.parse(response)
536
- @aws.last_errors = error_parser.errors
537
- @aws.last_request_id = error_parser.requestID
538
- last_errors_text = @aws.last_errors.flatten.join("\n")
539
- # on redirect :
540
- if redirect_detected
541
- location = response['location']
542
- # ... log information and ...
543
- @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
544
- @aws.logger.info("##### New location: #{location} #####")
545
- # ... fix the connection data
546
- request[:server] = URI.parse(location).host
547
- request[:protocol] = URI.parse(location).scheme
548
- request[:port] = URI.parse(location).port
549
- end
698
+ error_parser.parse(response.body)
550
699
  end
551
- else # ... it is not a xml document(probably just a html page?)
700
+ @aws.last_errors = error_parser.errors
701
+ @aws.last_request_id = error_parser.requestID
702
+ last_errors_text = @aws.last_errors.flatten.join("\n")
703
+ else
552
704
  @aws.last_errors = [[response.code, "#{response.message} (#{request_text_data})"]]
553
705
  @aws.last_request_id = '-undefined-'
554
706
  last_errors_text = response.message
555
707
  end
556
- # now - check the error
557
- unless redirect_detected
708
+
709
+ # Ok, it is a redirect, find the new destination location
710
+ if redirect_detected
711
+ location = response['location']
712
+ # ... log information and ...
713
+ @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
714
+ @aws.logger.info(" Old location: #{request_text_data}")
715
+ @aws.logger.info(" New location: #{location}")
716
+ # ... fix the connection data
717
+ request[:server] = URI.parse(location).host
718
+ request[:protocol] = URI.parse(location).scheme
719
+ request[:port] = URI.parse(location).port
720
+ else
721
+ # Not a redirect but an error: try to find the error in our list
558
722
  @errors_list.each do |error_to_find|
559
723
  if last_errors_text[/#{error_to_find}/i]
560
724
  error_found = true
@@ -564,6 +728,7 @@ module RightAws
564
728
  end
565
729
  end
566
730
  end
731
+
567
732
  # check the time has gone from the first error come
568
733
  if redirect_detected || error_found
569
734
  # Close the connection to the server and recreate a new one.
@@ -627,7 +792,7 @@ module RightAws
627
792
  @right_aws_parser.tag_start(name, attr_hash)
628
793
  end
629
794
  def on_characters(chars)
630
- @right_aws_parser.text(chars)
795
+ @right_aws_parser.text(chars)
631
796
  end
632
797
  def on_end_element(name)
633
798
  @right_aws_parser.tag_end(name)
@@ -656,25 +821,30 @@ module RightAws
656
821
  attr_accessor :result
657
822
  attr_reader :xmlpath
658
823
  attr_accessor :xml_lib
824
+ attr_reader :full_tag_name
825
+ attr_reader :tag
659
826
 
660
827
  def initialize(params={})
661
828
  @xmlpath = ''
829
+ @full_tag_name = ''
662
830
  @result = false
663
831
  @text = ''
832
+ @tag = ''
664
833
  @xml_lib = params[:xml_lib] || @@xml_lib
665
834
  @logger = params[:logger]
666
835
  reset
667
836
  end
668
837
  def tag_start(name, attributes)
669
838
  @text = ''
839
+ @tag = name
840
+ @full_tag_name += @full_tag_name.empty? ? name : "/#{name}"
670
841
  tagstart(name, attributes)
671
- @xmlpath += @xmlpath.empty? ? name : "/#{name}"
842
+ @xmlpath = @full_tag_name
672
843
  end
673
844
  def tag_end(name)
674
- if @xmlpath =~ /^(.*?)\/?#{name}$/
675
- @xmlpath = $1
676
- end
845
+ @xmlpath = @full_tag_name[/^(.*?)\/?#{name}$/] && $1
677
846
  tagend(name)
847
+ @full_tag_name = @xmlpath
678
848
  end
679
849
  def text(text)
680
850
  @text += text
@@ -711,14 +881,19 @@ module RightAws
711
881
  # Parse the xml text
712
882
  case @xml_lib
713
883
  when 'libxml'
714
- xml = XML::SaxParser.new
715
- xml.string = xml_text
884
+ if XML::Parser::VERSION >= '0.9.9'
885
+ # avoid warning on every usage
886
+ xml = XML::SaxParser.string(xml_text)
887
+ else
888
+ xml = XML::SaxParser.new
889
+ xml.string = xml_text
890
+ end
716
891
  # check libxml-ruby version
717
892
  if XML::Parser::VERSION >= '0.5.1.0'
718
893
  xml.callbacks = RightSaxParserCallback.new(self)
719
894
  else
720
895
  xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
721
- xml.on_characters{ |text| self.text(text)}
896
+ xml.on_characters{ |text| self.text(text)}
722
897
  xml.on_end_element{ |name| self.tag_end(name)}
723
898
  end
724
899
  xml.parse
@@ -793,5 +968,11 @@ module RightAws
793
968
  end
794
969
  end
795
970
 
971
+ class RightBoolResponseParser < RightAWSParser #:nodoc:
972
+ def tagend(name)
973
+ @result = (@text=='true') if name == 'return'
974
+ end
975
+ end
976
+
796
977
  end
797
978
 
@@ -47,6 +47,10 @@ unless defined? ActiveSupport::CoreExtensions
47
47
  Object.module_eval("::#{$1}", __FILE__, __LINE__)
48
48
  end
49
49
 
50
+ def camelize()
51
+ self.dup.split(/_/).map{ |word| word.capitalize }.join('')
52
+ end
53
+
50
54
  end
51
55
 
52
56
 
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2007-2008 RightScale Inc
2
+ # Copyright (c) 2007-2009 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -68,7 +68,7 @@ module RightAws
68
68
  include RightAwsBaseInterface
69
69
 
70
70
  # Amazon EC2 API version being used
71
- API_VERSION = "2008-12-01"
71
+ API_VERSION = "2009-11-30"
72
72
  DEFAULT_HOST = "ec2.amazonaws.com"
73
73
  DEFAULT_PATH = '/'
74
74
  DEFAULT_PROTOCOL = 'https'
@@ -81,7 +81,7 @@ module RightAws
81
81
  # Amazon EC2 Instance Types : http://www.amazon.com/b?ie=UTF8&node=370375011
82
82
  # Default EC2 instance type (platform)
83
83
  DEFAULT_INSTANCE_TYPE = 'm1.small'
84
- INSTANCE_TYPES = ['m1.small','c1.medium','m1.large','m1.xlarge','c1.xlarge']
84
+ INSTANCE_TYPES = ['m1.small','c1.medium','m1.large','m1.xlarge','c1.xlarge', 'm2.xlarge', 'm2.2xlarge', 'm2.4xlarge']
85
85
 
86
86
  @@bench = AwsBenchmarkingBlock.new
87
87
  def self.bench_xml
@@ -107,753 +107,36 @@ module RightAws
107
107
  # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
108
108
  # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
109
109
  # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
110
- # * <tt>:signature_version</tt>: The signature version : '0' or '1'(default)
110
+ # * <tt>:signature_version</tt>: The signature version : '0','1' or '2'(default)
111
111
  # * <tt>:cache</tt>: true/false: caching for: ec2_describe_images, describe_instances,
112
112
  # describe_images_by_owner, describe_images_by_executable_by, describe_availability_zones,
113
113
  # describe_security_groups, describe_key_pairs, describe_addresses,
114
114
  # describe_volumes, describe_snapshots methods, default: false.
115
115
  #
116
116
  def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
117
- init({ :name => 'EC2',
118
- :default_host => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).host : DEFAULT_HOST,
119
- :default_port => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).port : DEFAULT_PORT,
120
- :default_service => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).path : DEFAULT_PATH,
121
- :default_protocol => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).scheme : DEFAULT_PROTOCOL },
117
+ init({ :name => 'EC2',
118
+ :default_host => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).host : DEFAULT_HOST,
119
+ :default_port => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).port : DEFAULT_PORT,
120
+ :default_service => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).path : DEFAULT_PATH,
121
+ :default_protocol => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).scheme : DEFAULT_PROTOCOL,
122
+ :default_api_version => @@api },
122
123
  aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
123
124
  aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
124
125
  params)
125
- # EC2 doesn't really define any transient errors to retry, and in fact,
126
- # when they return a 503 it is usually for 'request limit exceeded' which
127
- # we most certainly should not retry. So let's pare down the list of
128
- # retryable errors to InternalError only (see RightAwsBase for the default
129
- # list)
130
- amazon_problems = ['InternalError']
126
+ # Eucalyptus supports some yummy features but Amazon does not
127
+ if @params[:eucalyptus]
128
+ @params[:port_based_group_ingress] = true unless @params.has_key?(:port_based_group_ingress)
129
+ end
131
130
  end
132
131
 
133
-
134
132
  def generate_request(action, params={}) #:nodoc:
135
- service_hash = {"Action" => action,
136
- "AWSAccessKeyId" => @aws_access_key_id,
137
- "Version" => @@api }
138
- service_hash.update(params)
139
- service_params = signed_service_params(@aws_secret_access_key, service_hash, :get, @params[:server], @params[:service])
140
-
141
- # use POST method if the length of the query string is too large
142
- if service_params.size > 2000
143
- if signature_version == '2'
144
- # resign the request because HTTP verb is included into signature
145
- service_params = signed_service_params(@aws_secret_access_key, service_hash, :post, @params[:server], @params[:service])
146
- end
147
- request = Net::HTTP::Post.new(service)
148
- request.body = service_params
149
- request['Content-Type'] = 'application/x-www-form-urlencoded'
150
- else
151
- request = Net::HTTP::Get.new("#{@params[:service]}?#{service_params}")
152
- end
153
- # prepare output hash
154
- { :request => request,
155
- :server => @params[:server],
156
- :port => @params[:port],
157
- :protocol => @params[:protocol] }
133
+ generate_request_impl(:get, action, params )
158
134
  end
159
135
 
160
136
  # Sends request to Amazon and parses the response
161
137
  # Raises AwsError if any banana happened
162
138
  def request_info(request, parser) #:nodoc:
163
- thread = @params[:multi_thread] ? Thread.current : Thread.main
164
- thread[:ec2_connection] ||= Rightscale::HttpConnection.new(:exception => AwsError, :logger => @logger)
165
- request_info_impl(thread[:ec2_connection], @@bench, request, parser)
166
- end
167
-
168
- def hash_params(prefix, list) #:nodoc:
169
- groups = {}
170
- list.each_index{|i| groups.update("#{prefix}.#{i+1}"=>list[i])} if list
171
- return groups
172
- end
173
-
174
- #-----------------------------------------------------------------
175
- # Images
176
- #-----------------------------------------------------------------
177
-
178
- # params:
179
- # { 'ImageId' => ['id1', ..., 'idN'],
180
- # 'Owner' => ['self', ..., 'userN'],
181
- # 'ExecutableBy' => ['self', 'all', ..., 'userN']
182
- # }
183
- def ec2_describe_images(params={}, image_type=nil, cache_for=nil) #:nodoc:
184
- request_hash = {}
185
- params.each do |list_by, list|
186
- request_hash.merge! hash_params(list_by, list.to_a)
187
- end
188
- request_hash['ImageType'] = image_type if image_type
189
- link = generate_request("DescribeImages", request_hash)
190
- request_cache_or_info cache_for, link, QEc2DescribeImagesParser, @@bench, cache_for
191
- rescue Exception
192
- on_exception
193
- end
194
-
195
- # Retrieve a list of images. Returns array of hashes describing the images or an exception:
196
- # +image_type+ = 'machine' || 'kernel' || 'ramdisk'
197
- #
198
- # ec2.describe_images #=>
199
- # [{:aws_owner => "522821470517",
200
- # :aws_id => "ami-e4b6538d",
201
- # :aws_state => "available",
202
- # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml",
203
- # :aws_is_public => true,
204
- # :aws_architecture => "i386",
205
- # :aws_image_type => "machine"},
206
- # {...},
207
- # {...} ]
208
- #
209
- # If +list+ param is set, then retrieve information about the listed images only:
210
- #
211
- # ec2.describe_images(['ami-e4b6538d']) #=>
212
- # [{:aws_owner => "522821470517",
213
- # :aws_id => "ami-e4b6538d",
214
- # :aws_state => "available",
215
- # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml",
216
- # :aws_is_public => true,
217
- # :aws_architecture => "i386",
218
- # :aws_image_type => "machine"}]
219
- #
220
- def describe_images(list=[], image_type=nil)
221
- list = list.to_a
222
- cache_for = list.empty? && !image_type ? :describe_images : nil
223
- ec2_describe_images({ 'ImageId' => list }, image_type, cache_for)
224
- end
225
-
226
- #
227
- # Example:
228
- #
229
- # ec2.describe_images_by_owner('522821470517')
230
- # ec2.describe_images_by_owner('self')
231
- #
232
- def describe_images_by_owner(list=['self'], image_type=nil)
233
- list = list.to_a
234
- cache_for = list==['self'] && !image_type ? :describe_images_by_owner : nil
235
- ec2_describe_images({ 'Owner' => list }, image_type, cache_for)
236
- end
237
-
238
- #
239
- # Example:
240
- #
241
- # ec2.describe_images_by_executable_by('522821470517')
242
- # ec2.describe_images_by_executable_by('self')
243
- # ec2.describe_images_by_executable_by('all')
244
- #
245
- def describe_images_by_executable_by(list=['self'], image_type=nil)
246
- list = list.to_a
247
- cache_for = list==['self'] && !image_type ? :describe_images_by_executable_by : nil
248
- ec2_describe_images({ 'ExecutableBy' => list }, image_type, cache_for)
249
- end
250
-
251
-
252
- # Register new image at Amazon.
253
- # Returns new image id or an exception.
254
- #
255
- # ec2.register_image('bucket/key/manifest') #=> 'ami-e444444d'
256
- #
257
- def register_image(image_location)
258
- link = generate_request("RegisterImage",
259
- 'ImageLocation' => image_location.to_s)
260
- request_info(link, QEc2RegisterImageParser.new(:logger => @logger))
261
- rescue Exception
262
- on_exception
263
- end
264
-
265
- # Deregister image at Amazon. Returns +true+ or an exception.
266
- #
267
- # ec2.deregister_image('ami-e444444d') #=> true
268
- #
269
- def deregister_image(image_id)
270
- link = generate_request("DeregisterImage",
271
- 'ImageId' => image_id.to_s)
272
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
273
- rescue Exception
274
- on_exception
275
- end
276
-
277
-
278
- # Describe image attributes. Currently 'launchPermission', 'productCodes', 'kernel', 'ramdisk' and 'blockDeviceMapping' are supported.
279
- #
280
- # ec2.describe_image_attribute('ami-e444444d') #=> {:groups=>["all"], :users=>["000000000777"]}
281
- #
282
- def describe_image_attribute(image_id, attribute='launchPermission')
283
- link = generate_request("DescribeImageAttribute",
284
- 'ImageId' => image_id,
285
- 'Attribute' => attribute)
286
- request_info(link, QEc2DescribeImageAttributeParser.new(:logger => @logger))
287
- rescue Exception
288
- on_exception
289
- end
290
-
291
- # Reset image attribute. Currently, only 'launchPermission' is supported. Returns +true+ or an exception.
292
- #
293
- # ec2.reset_image_attribute('ami-e444444d') #=> true
294
- #
295
- def reset_image_attribute(image_id, attribute='launchPermission')
296
- link = generate_request("ResetImageAttribute",
297
- 'ImageId' => image_id,
298
- 'Attribute' => attribute)
299
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
300
- rescue Exception
301
- on_exception
302
- end
303
-
304
- # Modify an image's attributes. It is recommended that you use
305
- # modify_image_launch_perm_add_users, modify_image_launch_perm_remove_users, etc.
306
- # instead of modify_image_attribute because the signature of
307
- # modify_image_attribute may change with EC2 service changes.
308
- #
309
- # attribute : currently, only 'launchPermission' is supported.
310
- # operation_type : currently, only 'add' & 'remove' are supported.
311
- # vars:
312
- # :user_group : currently, only 'all' is supported.
313
- # :user_id
314
- # :product_code
315
- def modify_image_attribute(image_id, attribute, operation_type = nil, vars = {})
316
- params = {'ImageId' => image_id,
317
- 'Attribute' => attribute}
318
- params['OperationType'] = operation_type if operation_type
319
- params.update(hash_params('UserId', vars[:user_id].to_a)) if vars[:user_id]
320
- params.update(hash_params('UserGroup', vars[:user_group].to_a)) if vars[:user_group]
321
- params.update(hash_params('ProductCode', vars[:product_code])) if vars[:product_code]
322
- link = generate_request("ModifyImageAttribute", params)
323
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
324
- rescue Exception
325
- on_exception
326
- end
327
-
328
- # Grant image launch permissions to users.
329
- # Parameter +userId+ is a list of user AWS account ids.
330
- # Returns +true+ or an exception.
331
- #
332
- # ec2.modify_image_launch_perm_add_users('ami-e444444d',['000000000777','000000000778']) #=> true
333
- def modify_image_launch_perm_add_users(image_id, user_id=[])
334
- modify_image_attribute(image_id, 'launchPermission', 'add', :user_id => user_id.to_a)
335
- end
336
-
337
- # Revokes image launch permissions for users. +userId+ is a list of users AWS accounts ids. Returns +true+ or an exception.
338
- #
339
- # ec2.modify_image_launch_perm_remove_users('ami-e444444d',['000000000777','000000000778']) #=> true
340
- #
341
- def modify_image_launch_perm_remove_users(image_id, user_id=[])
342
- modify_image_attribute(image_id, 'launchPermission', 'remove', :user_id => user_id.to_a)
343
- end
344
-
345
- # Add image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
346
- # Returns +true+ or an exception.
347
- #
348
- # ec2.modify_image_launch_perm_add_groups('ami-e444444d') #=> true
349
- #
350
- def modify_image_launch_perm_add_groups(image_id, user_group=['all'])
351
- modify_image_attribute(image_id, 'launchPermission', 'add', :user_group => user_group.to_a)
352
- end
353
-
354
- # Remove image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
355
- #
356
- # ec2.modify_image_launch_perm_remove_groups('ami-e444444d') #=> true
357
- #
358
- def modify_image_launch_perm_remove_groups(image_id, user_group=['all'])
359
- modify_image_attribute(image_id, 'launchPermission', 'remove', :user_group => user_group.to_a)
360
- end
361
-
362
- # Add product code to image
363
- #
364
- # ec2.modify_image_product_code('ami-e444444d','0ABCDEF') #=> true
365
- #
366
- def modify_image_product_code(image_id, product_code=[])
367
- modify_image_attribute(image_id, 'productCodes', nil, :product_code => product_code.to_a)
368
- end
369
-
370
- #-----------------------------------------------------------------
371
- # Instances
372
- #-----------------------------------------------------------------
373
-
374
- def get_desc_instances(instances) # :nodoc:
375
- result = []
376
- instances.each do |reservation|
377
- reservation[:instances_set].each do |instance|
378
- # Parse and remove timestamp from the reason string. The timestamp is of
379
- # the request, not when EC2 took action, thus confusing & useless...
380
- instance[:aws_reason] = instance[:aws_reason].sub(/\(\d[^)]*GMT\) */, '')
381
- instance[:aws_owner] = reservation[:aws_owner]
382
- instance[:aws_reservation_id] = reservation[:aws_reservation_id]
383
- instance[:aws_groups] = reservation[:aws_groups]
384
- result << instance
385
- end
386
- end
387
- result
388
- rescue Exception
389
- on_exception
390
- end
391
-
392
- # Retrieve information about EC2 instances. If +list+ is omitted then returns the
393
- # list of all instances.
394
- #
395
- # ec2.describe_instances #=>
396
- # [{:aws_image_id => "ami-e444444d",
397
- # :aws_reason => "",
398
- # :aws_state_code => "16",
399
- # :aws_owner => "000000000888",
400
- # :aws_instance_id => "i-123f1234",
401
- # :aws_reservation_id => "r-aabbccdd",
402
- # :aws_state => "running",
403
- # :dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
404
- # :ssh_key_name => "staging",
405
- # :aws_groups => ["default"],
406
- # :private_dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
407
- # :aws_instance_type => "m1.small",
408
- # :aws_launch_time => "2008-1-1T00:00:00.000Z"},
409
- # :aws_availability_zone => "us-east-1b",
410
- # :aws_kernel_id => "aki-ba3adfd3",
411
- # :aws_ramdisk_id => "ari-badbad00",
412
- # ..., {...}]
413
- #
414
- def describe_instances(list=[])
415
- link = generate_request("DescribeInstances", hash_params('InstanceId',list.to_a))
416
- request_cache_or_info(:describe_instances, link, QEc2DescribeInstancesParser, @@bench, list.blank?) do |parser|
417
- get_desc_instances(parser.result)
418
- end
419
- rescue Exception
420
- on_exception
421
- end
422
-
423
- # Return the product code attached to instance or +nil+ otherwise.
424
- #
425
- # ec2.confirm_product_instance('ami-e444444d','12345678') #=> nil
426
- # ec2.confirm_product_instance('ami-e444444d','00001111') #=> "000000000888"
427
- #
428
- def confirm_product_instance(instance, product_code)
429
- link = generate_request("ConfirmProductInstance", { 'ProductCode' => product_code,
430
- 'InstanceId' => instance })
431
- request_info(link, QEc2ConfirmProductInstanceParser.new(:logger => @logger))
432
- end
433
-
434
- # Launch new EC2 instances. Returns a list of launched instances or an exception.
435
- #
436
- # ec2.run_instances('ami-e444444d',1,1,['my_awesome_group'],'my_awesome_key', 'Woohoo!!!', 'public') #=>
437
- # [{:aws_image_id => "ami-e444444d",
438
- # :aws_reason => "",
439
- # :aws_state_code => "0",
440
- # :aws_owner => "000000000888",
441
- # :aws_instance_id => "i-123f1234",
442
- # :aws_reservation_id => "r-aabbccdd",
443
- # :aws_state => "pending",
444
- # :dns_name => "",
445
- # :ssh_key_name => "my_awesome_key",
446
- # :aws_groups => ["my_awesome_group"],
447
- # :private_dns_name => "",
448
- # :aws_instance_type => "m1.small",
449
- # :aws_launch_time => "2008-1-1T00:00:00.000Z"
450
- # :aws_ramdisk_id => "ari-8605e0ef"
451
- # :aws_kernel_id => "aki-9905e0f0",
452
- # :ami_launch_index => "0",
453
- # :aws_availability_zone => "us-east-1b"
454
- # }]
455
- #
456
- def run_instances(image_id, min_count, max_count, group_ids, key_name, user_data='',
457
- addressing_type = nil, instance_type = nil,
458
- kernel_id = nil, ramdisk_id = nil, availability_zone = nil,
459
- block_device_mappings = nil)
460
- launch_instances(image_id, { :min_count => min_count,
461
- :max_count => max_count,
462
- :user_data => user_data,
463
- :group_ids => group_ids,
464
- :key_name => key_name,
465
- :instance_type => instance_type,
466
- :addressing_type => addressing_type,
467
- :kernel_id => kernel_id,
468
- :ramdisk_id => ramdisk_id,
469
- :availability_zone => availability_zone,
470
- :block_device_mappings => block_device_mappings
471
- })
472
- end
473
-
474
-
475
- # Launch new EC2 instances. Returns a list of launched instances or an exception.
476
- #
477
- # +lparams+ keys (default values in parenthesis):
478
- # :min_count fixnum, (1)
479
- # :max_count fixnum, (1)
480
- # :group_ids array or string ([] == 'default')
481
- # :instance_type string (DEFAULT_INSTACE_TYPE)
482
- # :addressing_type string (DEFAULT_ADDRESSING_TYPE
483
- # :key_name string
484
- # :kernel_id string
485
- # :ramdisk_id string
486
- # :availability_zone string
487
- # :block_device_mappings string
488
- # :user_data string
489
- #
490
- # ec2.launch_instances('ami-e444444d', :group_ids => 'my_awesome_group',
491
- # :user_data => "Woohoo!!!",
492
- # :addressing_type => "public",
493
- # :key_name => "my_awesome_key",
494
- # :availability_zone => "us-east-1c") #=>
495
- # [{:aws_image_id => "ami-e444444d",
496
- # :aws_reason => "",
497
- # :aws_state_code => "0",
498
- # :aws_owner => "000000000888",
499
- # :aws_instance_id => "i-123f1234",
500
- # :aws_reservation_id => "r-aabbccdd",
501
- # :aws_state => "pending",
502
- # :dns_name => "",
503
- # :ssh_key_name => "my_awesome_key",
504
- # :aws_groups => ["my_awesome_group"],
505
- # :private_dns_name => "",
506
- # :aws_instance_type => "m1.small",
507
- # :aws_launch_time => "2008-1-1T00:00:00.000Z",
508
- # :aws_ramdisk_id => "ari-8605e0ef"
509
- # :aws_kernel_id => "aki-9905e0f0",
510
- # :ami_launch_index => "0",
511
- # :aws_availability_zone => "us-east-1c"
512
- # }]
513
- #
514
- def launch_instances(image_id, lparams={})
515
- @logger.info("Launching instance of image #{image_id} for #{@aws_access_key_id}, " +
516
- "key: #{lparams[:key_name]}, groups: #{(lparams[:group_ids]).to_a.join(',')}")
517
- # careful: keyName and securityGroups may be nil
518
- params = hash_params('SecurityGroup', lparams[:group_ids].to_a)
519
- params.update( {'ImageId' => image_id,
520
- 'MinCount' => (lparams[:min_count] || 1).to_s,
521
- 'MaxCount' => (lparams[:max_count] || 1).to_s,
522
- 'AddressingType' => lparams[:addressing_type] || DEFAULT_ADDRESSING_TYPE,
523
- 'InstanceType' => lparams[:instance_type] || DEFAULT_INSTANCE_TYPE })
524
- # optional params
525
- params['KeyName'] = lparams[:key_name] unless lparams[:key_name].blank?
526
- params['KernelId'] = lparams[:kernel_id] unless lparams[:kernel_id].blank?
527
- params['RamdiskId'] = lparams[:ramdisk_id] unless lparams[:ramdisk_id].blank?
528
- params['Placement.AvailabilityZone'] = lparams[:availability_zone] unless lparams[:availability_zone].blank?
529
- params['BlockDeviceMappings'] = lparams[:block_device_mappings] unless lparams[:block_device_mappings].blank?
530
- unless lparams[:user_data].blank?
531
- lparams[:user_data].strip!
532
- # Do not use CGI::escape(encode64(...)) as it is done in Amazons EC2 library.
533
- # Amazon 169.254.169.254 does not like escaped symbols!
534
- # And it doesn't like "\n" inside of encoded string! Grrr....
535
- # Otherwise, some of UserData symbols will be lost...
536
- params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n").strip unless lparams[:user_data].blank?
537
- end
538
- link = generate_request("RunInstances", params)
539
- #debugger
540
- instances = request_info(link, QEc2DescribeInstancesParser.new(:logger => @logger))
541
- get_desc_instances(instances)
542
- rescue Exception
543
- on_exception
544
- end
545
-
546
- # Terminates EC2 instances. Returns a list of termination params or an exception.
547
- #
548
- # ec2.terminate_instances(['i-f222222d','i-f222222e']) #=>
549
- # [{:aws_shutdown_state => "shutting-down",
550
- # :aws_instance_id => "i-f222222d",
551
- # :aws_shutdown_state_code => 32,
552
- # :aws_prev_state => "running",
553
- # :aws_prev_state_code => 16},
554
- # {:aws_shutdown_state => "shutting-down",
555
- # :aws_instance_id => "i-f222222e",
556
- # :aws_shutdown_state_code => 32,
557
- # :aws_prev_state => "running",
558
- # :aws_prev_state_code => 16}]
559
- #
560
- def terminate_instances(list=[])
561
- link = generate_request("TerminateInstances", hash_params('InstanceId',list.to_a))
562
- request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
563
- rescue Exception
564
- on_exception
565
- end
566
-
567
- # Retreive EC2 instance OS logs. Returns a hash of data or an exception.
568
- #
569
- # ec2.get_console_output('i-f222222d') =>
570
- # {:aws_instance_id => 'i-f222222d',
571
- # :aws_timestamp => "2007-05-23T14:36:07.000-07:00",
572
- # :timestamp => Wed May 23 21:36:07 UTC 2007, # Time instance
573
- # :aws_output => "Linux version 2.6.16-xenU (builder@patchbat.amazonsa) (gcc version 4.0.1 20050727 ..."
574
- def get_console_output(instance_id)
575
- link = generate_request("GetConsoleOutput", { 'InstanceId.1' => instance_id })
576
- request_info(link, QEc2GetConsoleOutputParser.new(:logger => @logger))
577
- rescue Exception
578
- on_exception
579
- end
580
-
581
- # Reboot an EC2 instance. Returns +true+ or an exception.
582
- #
583
- # ec2.reboot_instances(['i-f222222d','i-f222222e']) #=> true
584
- #
585
- def reboot_instances(list)
586
- link = generate_request("RebootInstances", hash_params('InstanceId', list.to_a))
587
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
588
- rescue Exception
589
- on_exception
590
- end
591
-
592
- #-----------------------------------------------------------------
593
- # Instances: Windows addons
594
- #-----------------------------------------------------------------
595
-
596
- # Get initial Windows Server setup password from an instance console output.
597
- #
598
- # my_awesome_key = ec2.create_key_pair('my_awesome_key') #=>
599
- # {:aws_key_name => "my_awesome_key",
600
- # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03",
601
- # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"}
602
- #
603
- # my_awesome_instance = ec2.run_instances('ami-a000000a',1,1,['my_awesome_group'],'my_awesome_key', 'WindowsInstance!!!') #=>
604
- # [{:aws_image_id => "ami-a000000a",
605
- # :aws_instance_id => "i-12345678",
606
- # ...
607
- # :aws_availability_zone => "us-east-1b"
608
- # }]
609
- #
610
- # # wait until instance enters 'operational' state and get it's initial password
611
- #
612
- # puts ec2.get_initial_password(my_awesome_instance[:aws_instance_id], my_awesome_key[:aws_material]) #=> "MhjWcgZuY6"
613
- #
614
- def get_initial_password(instance_id, private_key)
615
- console_output = get_console_output(instance_id)
616
- crypted_password = console_output[:aws_output][%r{<Password>(.+)</Password>}m] && $1
617
- unless crypted_password
618
- raise AwsError.new("Initial password was not found in console output for #{instance_id}")
619
- else
620
- OpenSSL::PKey::RSA.new(private_key).private_decrypt(Base64.decode64(crypted_password))
621
- end
622
- rescue Exception
623
- on_exception
624
- end
625
-
626
- # Bundle a Windows image.
627
- # Internally, it queues the bundling task and shuts down the instance.
628
- # It then takes a snapshot of the Windows volume bundles it, and uploads it to
629
- # S3. After bundling completes, Rightaws::Ec2#register_image may be used to
630
- # register the new Windows AMI for subsequent launches.
631
- #
632
- # ec2.bundle_instance('i-e3e24e8a', 'my-awesome-bucket', 'my-win-image-1') #=>
633
- # [{:aws_update_time => "2008-10-16T13:58:25.000Z",
634
- # :s3_bucket => "kd-win-1",
635
- # :s3_prefix => "win2pr",
636
- # :aws_state => "pending",
637
- # :aws_id => "bun-26a7424f",
638
- # :aws_instance_id => "i-878a25ee",
639
- # :aws_start_time => "2008-10-16T13:58:02.000Z"}]
640
- #
641
- def bundle_instance(instance_id, s3_bucket, s3_prefix,
642
- s3_owner_aws_access_key_id=nil, s3_owner_aws_secret_access_key=nil,
643
- s3_expires = S3Interface::DEFAULT_EXPIRES_AFTER,
644
- s3_upload_policy='ec2-bundle-read')
645
- # S3 access and signatures
646
- s3_owner_aws_access_key_id ||= @aws_access_key_id
647
- s3_owner_aws_secret_access_key ||= @aws_secret_access_key
648
- s3_expires = Time.now.utc + s3_expires if s3_expires.is_a?(Fixnum) && (s3_expires < S3Interface::ONE_YEAR_IN_SECONDS)
649
- # policy
650
- policy = { 'expiration' => s3_expires.strftime('%Y-%m-%dT%H:%M:%SZ'),
651
- 'conditions' => [ { 'bucket' => s3_bucket },
652
- { 'acl' => s3_upload_policy },
653
- [ 'starts-with', '$key', s3_prefix ] ] }.to_json
654
- policy64 = Base64.encode64(policy).gsub("\n","")
655
- signed_policy64 = AwsUtils.sign(s3_owner_aws_secret_access_key, policy64)
656
- # fill request params
657
- params = { 'InstanceId' => instance_id,
658
- 'Storage.S3.AWSAccessKeyId' => s3_owner_aws_access_key_id,
659
- 'Storage.S3.UploadPolicy' => policy64,
660
- 'Storage.S3.UploadPolicySignature' => signed_policy64,
661
- 'Storage.S3.Bucket' => s3_bucket,
662
- 'Storage.S3.Prefix' => s3_prefix,
663
- }
664
- link = generate_request("BundleInstance", params)
665
- request_info(link, QEc2BundleInstanceParser.new)
666
- rescue Exception
667
- on_exception
668
- end
669
-
670
- # Describe the status of the Windows AMI bundlings.
671
- # If +list+ is omitted the returns the whole list of tasks.
672
- #
673
- # ec2.describe_bundle_tasks(['bun-4fa74226']) #=>
674
- # [{:s3_bucket => "my-awesome-bucket"
675
- # :aws_id => "bun-0fa70206",
676
- # :s3_prefix => "win1pr",
677
- # :aws_start_time => "2008-10-14T16:27:57.000Z",
678
- # :aws_update_time => "2008-10-14T16:37:10.000Z",
679
- # :aws_error_code => "Client.S3Error",
680
- # :aws_error_message =>
681
- # "AccessDenied(403)- Invalid according to Policy: Policy Condition failed: [\"eq\", \"$acl\", \"aws-exec-read\"]",
682
- # :aws_state => "failed",
683
- # :aws_instance_id => "i-e3e24e8a"}]
684
- #
685
- def describe_bundle_tasks(list=[])
686
- link = generate_request("DescribeBundleTasks", hash_params('BundleId', list.to_a))
687
- request_info(link, QEc2DescribeBundleTasksParser.new)
688
- rescue Exception
689
- on_exception
690
- end
691
-
692
- # Cancel an in‐progress or pending bundle task by id.
693
- #
694
- # ec2.cancel_bundle_task('bun-73a7421a') #=>
695
- # [{:s3_bucket => "my-awesome-bucket"
696
- # :aws_id => "bun-0fa70206",
697
- # :s3_prefix => "win02",
698
- # :aws_start_time => "2008-10-14T13:00:29.000Z",
699
- # :aws_error_message => "User has requested bundling operation cancellation",
700
- # :aws_state => "failed",
701
- # :aws_update_time => "2008-10-14T13:01:31.000Z",
702
- # :aws_error_code => "Client.Cancelled",
703
- # :aws_instance_id => "i-e3e24e8a"}
704
- #
705
- def cancel_bundle_task(bundle_id)
706
- link = generate_request("CancelBundleTask", { 'BundleId' => bundle_id })
707
- request_info(link, QEc2BundleInstanceParser.new)
708
- rescue Exception
709
- on_exception
710
- end
711
-
712
- #-----------------------------------------------------------------
713
- # Security groups
714
- #-----------------------------------------------------------------
715
-
716
- # Retrieve Security Group information. If +list+ is omitted the returns the whole list of groups.
717
- #
718
- # ec2.describe_security_groups #=>
719
- # [{:aws_group_name => "default-1",
720
- # :aws_owner => "000000000888",
721
- # :aws_description => "Default allowing SSH, HTTP, and HTTPS ingress",
722
- # :aws_perms =>
723
- # [{:owner => "000000000888", :group => "default"},
724
- # {:owner => "000000000888", :group => "default-1"},
725
- # {:to_port => "-1", :protocol => "icmp", :from_port => "-1", :cidr_ips => "0.0.0.0/0"},
726
- # {:to_port => "22", :protocol => "tcp", :from_port => "22", :cidr_ips => "0.0.0.0/0"},
727
- # {:to_port => "80", :protocol => "tcp", :from_port => "80", :cidr_ips => "0.0.0.0/0"},
728
- # {:to_port => "443", :protocol => "tcp", :from_port => "443", :cidr_ips => "0.0.0.0/0"}]},
729
- # ..., {...}]
730
- #
731
- def describe_security_groups(list=[])
732
- link = generate_request("DescribeSecurityGroups", hash_params('GroupName',list.to_a))
733
- request_cache_or_info( :describe_security_groups, link, QEc2DescribeSecurityGroupsParser, @@bench, list.blank?) do |parser|
734
- result = []
735
- parser.result.each do |item|
736
- perms = []
737
- item.ipPermissions.each do |perm|
738
- perm.groups.each do |ngroup|
739
- perms << {:group => ngroup.groupName,
740
- :owner => ngroup.userId}
741
- end
742
- perm.ipRanges.each do |cidr_ip|
743
- perms << {:from_port => perm.fromPort,
744
- :to_port => perm.toPort,
745
- :protocol => perm.ipProtocol,
746
- :cidr_ips => cidr_ip}
747
- end
748
- end
749
-
750
- # delete duplication
751
- perms.each_index do |i|
752
- (0...i).each do |j|
753
- if perms[i] == perms[j] then perms[i] = nil; break; end
754
- end
755
- end
756
- perms.compact!
757
-
758
- result << {:aws_owner => item.ownerId,
759
- :aws_group_name => item.groupName,
760
- :aws_description => item.groupDescription,
761
- :aws_perms => perms}
762
-
763
- end
764
- result
765
- end
766
- rescue Exception
767
- on_exception
768
- end
769
-
770
- # Create new Security Group. Returns +true+ or an exception.
771
- #
772
- # ec2.create_security_group('default-1',"Default allowing SSH, HTTP, and HTTPS ingress") #=> true
773
- #
774
- def create_security_group(name, description)
775
- # EC2 doesn't like an empty description...
776
- description = " " if description.blank?
777
- link = generate_request("CreateSecurityGroup",
778
- 'GroupName' => name.to_s,
779
- 'GroupDescription' => description.to_s)
780
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
781
- rescue Exception
782
- on_exception
783
- end
784
-
785
- # Remove Security Group. Returns +true+ or an exception.
786
- #
787
- # ec2.delete_security_group('default-1') #=> true
788
- #
789
- def delete_security_group(name)
790
- link = generate_request("DeleteSecurityGroup",
791
- 'GroupName' => name.to_s)
792
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
793
- rescue Exception
794
- on_exception
795
- end
796
-
797
- # Authorize named ingress for security group. Allows instances that are member of someone
798
- # else's security group to open connections to instances in my group.
799
- #
800
- # ec2.authorize_security_group_named_ingress('my_awesome_group', '7011-0219-8268', 'their_group_name') #=> true
801
- #
802
- def authorize_security_group_named_ingress(name, owner, group)
803
- link = generate_request("AuthorizeSecurityGroupIngress",
804
- 'GroupName' => name.to_s,
805
- 'SourceSecurityGroupName' => group.to_s,
806
- 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
807
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
808
- rescue Exception
809
- on_exception
810
- end
811
-
812
- # Revoke named ingress for security group.
813
- #
814
- # ec2.revoke_security_group_named_ingress('my_awesome_group', aws_user_id, 'another_group_name') #=> true
815
- #
816
- def revoke_security_group_named_ingress(name, owner, group)
817
- link = generate_request("RevokeSecurityGroupIngress",
818
- 'GroupName' => name.to_s,
819
- 'SourceSecurityGroupName' => group.to_s,
820
- 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
821
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
822
- rescue Exception
823
- on_exception
824
- end
825
-
826
- # Add permission to a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp'.
827
- #
828
- # ec2.authorize_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
829
- # ec2.authorize_security_group_IP_ingress('my_awesome_group', -1, -1, 'icmp') #=> true
830
- #
831
- def authorize_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
832
- link = generate_request("AuthorizeSecurityGroupIngress",
833
- 'GroupName' => name.to_s,
834
- 'IpProtocol' => protocol.to_s,
835
- 'FromPort' => from_port.to_s,
836
- 'ToPort' => to_port.to_s,
837
- 'CidrIp' => cidr_ip.to_s)
838
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
839
- rescue Exception
840
- on_exception
841
- end
842
-
843
- # Remove permission from a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp' ('tcp' is default).
844
- #
845
- # ec2.revoke_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
846
- #
847
- def revoke_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
848
- link = generate_request("RevokeSecurityGroupIngress",
849
- 'GroupName' => name.to_s,
850
- 'IpProtocol' => protocol.to_s,
851
- 'FromPort' => from_port.to_s,
852
- 'ToPort' => to_port.to_s,
853
- 'CidrIp' => cidr_ip.to_s)
854
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
855
- rescue Exception
856
- on_exception
139
+ request_info_impl(:ec2_connection, @@bench, request, parser)
857
140
  end
858
141
 
859
142
  #-----------------------------------------------------------------
@@ -868,9 +151,10 @@ module RightAws
868
151
  # {:aws_fingerprint=> "1e:29:30:47:58:6d:7b:8c:9f:08:11:20:3c:44:52:69:74:80:97:08", :aws_key_name=>"key-2"},
869
152
  # ..., {...} ]
870
153
  #
871
- def describe_key_pairs(list=[])
872
- link = generate_request("DescribeKeyPairs", hash_params('KeyName',list.to_a))
873
- request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, @@bench, list.blank?
154
+ def describe_key_pairs(*key_pairs)
155
+ key_pairs = key_pairs.flatten
156
+ link = generate_request("DescribeKeyPairs", amazonize_list('KeyName', key_pairs))
157
+ request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, @@bench, key_pairs.blank?
874
158
  rescue Exception
875
159
  on_exception
876
160
  end
@@ -940,10 +224,10 @@ module RightAws
940
224
  #
941
225
  # ec2.describe_addresses('75.101.154.140') #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"}]
942
226
  #
943
- def describe_addresses(list=[])
944
- link = generate_request("DescribeAddresses",
945
- hash_params('PublicIp',list.to_a))
946
- request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, @@bench, list.blank?
227
+ def describe_addresses(*addresses)
228
+ addresses = addresses.flatten
229
+ link = generate_request("DescribeAddresses", amazonize_list('PublicIp', addresses))
230
+ request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, @@bench, addresses.blank?
947
231
  rescue Exception
948
232
  on_exception
949
233
  end
@@ -989,10 +273,10 @@ module RightAws
989
273
  # :zone_state=>"available",
990
274
  # :zone_name=>"us-east-1c"}]
991
275
  #
992
- def describe_availability_zones(list=[])
993
- link = generate_request("DescribeAvailabilityZones",
994
- hash_params('ZoneName',list.to_a))
995
- request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, @@bench, list.blank?
276
+ def describe_availability_zones(*availability_zones)
277
+ availability_zones = availability_zones.flatten
278
+ link = generate_request("DescribeAvailabilityZones", amazonize_list('ZoneName', availability_zones))
279
+ request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, @@bench, availability_zones.blank?
996
280
  rescue Exception
997
281
  on_exception
998
282
  end
@@ -1005,227 +289,14 @@ module RightAws
1005
289
  #
1006
290
  # ec2.describe_regions #=> ["eu-west-1", "us-east-1"]
1007
291
  #
1008
- def describe_regions(list=[])
1009
- link = generate_request("DescribeRegions",
1010
- hash_params('RegionName',list.to_a))
1011
- request_cache_or_info :describe_regions, link, QEc2DescribeRegionsParser, @@bench, list.blank?
1012
- rescue Exception
1013
- on_exception
1014
- end
1015
-
1016
-
1017
- #-----------------------------------------------------------------
1018
- # EBS: Volumes
1019
- #-----------------------------------------------------------------
1020
-
1021
- # Describe all EBS volumes.
1022
- #
1023
- # ec2.describe_volumes #=>
1024
- # [{:aws_size => 94,
1025
- # :aws_device => "/dev/sdc",
1026
- # :aws_attachment_status => "attached",
1027
- # :zone => "merlot",
1028
- # :snapshot_id => nil,
1029
- # :aws_attached_at => Wed Jun 18 08:19:28 UTC 2008,
1030
- # :aws_status => "in-use",
1031
- # :aws_id => "vol-60957009",
1032
- # :aws_created_at => Wed Jun 18 08:19:20s UTC 2008,
1033
- # :aws_instance_id => "i-c014c0a9"},
1034
- # {:aws_size => 1,
1035
- # :zone => "merlot",
1036
- # :snapshot_id => nil,
1037
- # :aws_status => "available",
1038
- # :aws_id => "vol-58957031",
1039
- # :aws_created_at => Wed Jun 18 08:19:21 UTC 2008,}, ... ]
1040
- #
1041
- def describe_volumes(list=[])
1042
- link = generate_request("DescribeVolumes",
1043
- hash_params('VolumeId',list.to_a))
1044
- request_cache_or_info :describe_volumes, link, QEc2DescribeVolumesParser, @@bench, list.blank?
1045
- rescue Exception
1046
- on_exception
1047
- end
1048
-
1049
- # Create new EBS volume based on previously created snapshot.
1050
- # +Size+ in Gigabytes.
1051
- #
1052
- # ec2.create_volume('snap-000000', 10, zone) #=>
1053
- # {:snapshot_id => "snap-e21df98b",
1054
- # :aws_status => "creating",
1055
- # :aws_id => "vol-fc9f7a95",
1056
- # :zone => "merlot",
1057
- # :aws_created_at => Tue Jun 24 18:13:32 UTC 2008,
1058
- # :aws_size => 94}
1059
- #
1060
- def create_volume(snapshot_id, size, zone)
1061
- link = generate_request("CreateVolume",
1062
- "SnapshotId" => snapshot_id.to_s,
1063
- "Size" => size.to_s,
1064
- "AvailabilityZone" => zone.to_s )
1065
- request_info(link, QEc2CreateVolumeParser.new(:logger => @logger))
1066
- rescue Exception
1067
- on_exception
1068
- end
1069
-
1070
- # Delete the specified EBS volume.
1071
- # This does not deletes any snapshots created from this volume.
1072
- #
1073
- # ec2.delete_volume('vol-b48a6fdd') #=> true
1074
- #
1075
- def delete_volume(volume_id)
1076
- link = generate_request("DeleteVolume",
1077
- "VolumeId" => volume_id.to_s)
1078
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
1079
- rescue Exception
1080
- on_exception
1081
- end
1082
-
1083
- # Attach the specified EBS volume to a specified instance, exposing the
1084
- # volume using the specified device name.
1085
- #
1086
- # ec2.attach_volume('vol-898a6fe0', 'i-7c905415', '/dev/sdh') #=>
1087
- # { :aws_instance_id => "i-7c905415",
1088
- # :aws_device => "/dev/sdh",
1089
- # :aws_status => "attaching",
1090
- # :aws_attached_at => "2008-03-28T14:14:39.000Z",
1091
- # :aws_id => "vol-898a6fe0" }
1092
- #
1093
- def attach_volume(volume_id, instance_id, device)
1094
- link = generate_request("AttachVolume",
1095
- "VolumeId" => volume_id.to_s,
1096
- "InstanceId" => instance_id.to_s,
1097
- "Device" => device.to_s)
1098
- request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger))
1099
- rescue Exception
1100
- on_exception
1101
- end
1102
-
1103
- # Detach the specified EBS volume from the instance to which it is attached.
1104
- #
1105
- # ec2.detach_volume('vol-898a6fe0') #=>
1106
- # { :aws_instance_id => "i-7c905415",
1107
- # :aws_device => "/dev/sdh",
1108
- # :aws_status => "detaching",
1109
- # :aws_attached_at => "2008-03-28T14:38:34.000Z",
1110
- # :aws_id => "vol-898a6fe0"}
1111
- #
1112
- def detach_volume(volume_id, instance_id=nil, device=nil, force=nil)
1113
- hash = { "VolumeId" => volume_id.to_s }
1114
- hash["InstanceId"] = instance_id.to_s unless instance_id.blank?
1115
- hash["Device"] = device.to_s unless device.blank?
1116
- hash["Force"] = 'true' if force
1117
- #
1118
- link = generate_request("DetachVolume", hash)
1119
- request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger))
292
+ def describe_regions(*regions)
293
+ regions = regions.flatten
294
+ link = generate_request("DescribeRegions", amazonize_list('RegionName', regions))
295
+ request_cache_or_info :describe_regions, link, QEc2DescribeRegionsParser, @@bench, regions.blank?
1120
296
  rescue Exception
1121
297
  on_exception
1122
298
  end
1123
299
 
1124
-
1125
- #-----------------------------------------------------------------
1126
- # EBS: Snapshots
1127
- #-----------------------------------------------------------------
1128
-
1129
- # Describe all EBS snapshots.
1130
- #
1131
- # ec2.describe_snapshots #=>
1132
- # [ { :aws_progress => "100%",
1133
- # :aws_status => "completed",
1134
- # :aws_id => "snap-72a5401b",
1135
- # :aws_volume_id => "vol-5582673c",
1136
- # :aws_started_at => "2008-02-23T02:50:48.000Z"},
1137
- # { :aws_progress => "100%",
1138
- # :aws_status => "completed",
1139
- # :aws_id => "snap-75a5401c",
1140
- # :aws_volume_id => "vol-5582673c",
1141
- # :aws_started_at => "2008-02-23T16:23:19.000Z" },...]
1142
- #
1143
- def describe_snapshots(list=[])
1144
- link = generate_request("DescribeSnapshots",
1145
- hash_params('SnapshotId',list.to_a))
1146
- request_cache_or_info :describe_snapshots, link, QEc2DescribeSnapshotsParser, @@bench, list.blank?
1147
- rescue Exception
1148
- on_exception
1149
- end
1150
-
1151
- # Create a snapshot of specified volume.
1152
- #
1153
- # ec2.create_snapshot('vol-898a6fe0') #=>
1154
- # {:aws_volume_id => "vol-fd9f7a94",
1155
- # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1156
- # :aws_progress => "",
1157
- # :aws_status => "pending",
1158
- # :aws_id => "snap-d56783bc"}
1159
- #
1160
- def create_snapshot(volume_id)
1161
- link = generate_request("CreateSnapshot",
1162
- "VolumeId" => volume_id.to_s)
1163
- request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1164
- rescue Exception
1165
- on_exception
1166
- end
1167
-
1168
- # Create a snapshot of specified volume, but with the normal retry algorithms disabled.
1169
- # This method will return immediately upon error. The user can specify connect and read timeouts (in s)
1170
- # for the connection to AWS. If the user does not specify timeouts, try_create_snapshot uses the default values
1171
- # in Rightscale::HttpConnection.
1172
- #
1173
- # ec2.try_create_snapshot('vol-898a6fe0') #=>
1174
- # {:aws_volume_id => "vol-fd9f7a94",
1175
- # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1176
- # :aws_progress => "",
1177
- # :aws_status => "pending",
1178
- # :aws_id => "snap-d56783bc"}
1179
- #
1180
- def try_create_snapshot(volume_id, connect_timeout = nil, read_timeout = nil)
1181
- # For safety in the ensure block...we don't want to restore values
1182
- # if we never read them in the first place
1183
- orig_reiteration_time = nil
1184
- orig_http_params = nil
1185
-
1186
- orig_reiteration_time = RightAws::AWSErrorHandler::reiteration_time
1187
- RightAws::AWSErrorHandler::reiteration_time = 0
1188
-
1189
- orig_http_params = Rightscale::HttpConnection::params()
1190
- new_http_params = orig_http_params.dup
1191
- new_http_params[:http_connection_retry_count] = 0
1192
- new_http_params[:http_connection_open_timeout] = connect_timeout if !connect_timeout.nil?
1193
- new_http_params[:http_connection_read_timeout] = read_timeout if !read_timeout.nil?
1194
- Rightscale::HttpConnection::params = new_http_params
1195
-
1196
- link = generate_request("CreateSnapshot",
1197
- "VolumeId" => volume_id.to_s)
1198
- request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1199
-
1200
- rescue Exception
1201
- on_exception
1202
- ensure
1203
- RightAws::AWSErrorHandler::reiteration_time = orig_reiteration_time if orig_reiteration_time
1204
- Rightscale::HttpConnection::params = orig_http_params if orig_http_params
1205
- end
1206
-
1207
- # Delete the specified snapshot.
1208
- #
1209
- # ec2.delete_snapshot('snap-55a5403c') #=> true
1210
- #
1211
- def delete_snapshot(snapshot_id)
1212
- link = generate_request("DeleteSnapshot",
1213
- "SnapshotId" => snapshot_id.to_s)
1214
- request_info(link, RightBoolResponseParser.new(:logger => @logger))
1215
- rescue Exception
1216
- on_exception
1217
- end
1218
-
1219
- #-----------------------------------------------------------------
1220
- # PARSERS: Boolean Response Parser
1221
- #-----------------------------------------------------------------
1222
-
1223
- class RightBoolResponseParser < RightAWSParser #:nodoc:
1224
- def tagend(name)
1225
- @result = @text=='true' ? true : false if name == 'return'
1226
- end
1227
- end
1228
-
1229
300
  #-----------------------------------------------------------------
1230
301
  # PARSERS: Key Pair
1231
302
  #-----------------------------------------------------------------
@@ -1259,308 +330,6 @@ module RightAws
1259
330
  end
1260
331
  end
1261
332
 
1262
- #-----------------------------------------------------------------
1263
- # PARSERS: Security Groups
1264
- #-----------------------------------------------------------------
1265
-
1266
- class QEc2UserIdGroupPairType #:nodoc:
1267
- attr_accessor :userId
1268
- attr_accessor :groupName
1269
- end
1270
-
1271
- class QEc2IpPermissionType #:nodoc:
1272
- attr_accessor :ipProtocol
1273
- attr_accessor :fromPort
1274
- attr_accessor :toPort
1275
- attr_accessor :groups
1276
- attr_accessor :ipRanges
1277
- end
1278
-
1279
- class QEc2SecurityGroupItemType #:nodoc:
1280
- attr_accessor :groupName
1281
- attr_accessor :groupDescription
1282
- attr_accessor :ownerId
1283
- attr_accessor :ipPermissions
1284
- end
1285
-
1286
-
1287
- class QEc2DescribeSecurityGroupsParser < RightAWSParser #:nodoc:
1288
- def tagstart(name, attributes)
1289
- case name
1290
- when 'item'
1291
- if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo'
1292
- @group = QEc2SecurityGroupItemType.new
1293
- @group.ipPermissions = []
1294
- elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions'
1295
- @perm = QEc2IpPermissionType.new
1296
- @perm.ipRanges = []
1297
- @perm.groups = []
1298
- elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups'
1299
- @sgroup = QEc2UserIdGroupPairType.new
1300
- end
1301
- end
1302
- end
1303
- def tagend(name)
1304
- case name
1305
- when 'ownerId' then @group.ownerId = @text
1306
- when 'groupDescription' then @group.groupDescription = @text
1307
- when 'groupName'
1308
- if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item'
1309
- @group.groupName = @text
1310
- elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups/item'
1311
- @sgroup.groupName = @text
1312
- end
1313
- when 'ipProtocol' then @perm.ipProtocol = @text
1314
- when 'fromPort' then @perm.fromPort = @text
1315
- when 'toPort' then @perm.toPort = @text
1316
- when 'userId' then @sgroup.userId = @text
1317
- when 'cidrIp' then @perm.ipRanges << @text
1318
- when 'item'
1319
- if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups'
1320
- @perm.groups << @sgroup
1321
- elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions'
1322
- @group.ipPermissions << @perm
1323
- elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo'
1324
- @result << @group
1325
- end
1326
- end
1327
- end
1328
- def reset
1329
- @result = []
1330
- end
1331
- end
1332
-
1333
- #-----------------------------------------------------------------
1334
- # PARSERS: Images
1335
- #-----------------------------------------------------------------
1336
-
1337
- class QEc2DescribeImagesParser < RightAWSParser #:nodoc:
1338
- def tagstart(name, attributes)
1339
- if name == 'item' && @xmlpath[%r{.*/imagesSet$}]
1340
- @image = {}
1341
- end
1342
- end
1343
- def tagend(name)
1344
- case name
1345
- when 'imageId' then @image[:aws_id] = @text
1346
- when 'imageLocation' then @image[:aws_location] = @text
1347
- when 'imageState' then @image[:aws_state] = @text
1348
- when 'imageOwnerId' then @image[:aws_owner] = @text
1349
- when 'isPublic' then @image[:aws_is_public]= @text == 'true' ? true : false
1350
- when 'productCode' then (@image[:aws_product_codes] ||= []) << @text
1351
- when 'architecture' then @image[:aws_architecture] = @text
1352
- when 'imageType' then @image[:aws_image_type] = @text
1353
- when 'kernelId' then @image[:aws_kernel_id] = @text
1354
- when 'ramdiskId' then @image[:aws_ramdisk_id] = @text
1355
- when 'item' then @result << @image if @xmlpath[%r{.*/imagesSet$}]
1356
- end
1357
- end
1358
- def reset
1359
- @result = []
1360
- end
1361
- end
1362
-
1363
- class QEc2RegisterImageParser < RightAWSParser #:nodoc:
1364
- def tagend(name)
1365
- @result = @text if name == 'imageId'
1366
- end
1367
- end
1368
-
1369
- #-----------------------------------------------------------------
1370
- # PARSERS: Image Attribute
1371
- #-----------------------------------------------------------------
1372
-
1373
- class QEc2DescribeImageAttributeParser < RightAWSParser #:nodoc:
1374
- def tagstart(name, attributes)
1375
- case name
1376
- when 'launchPermission'
1377
- @result[:groups] = []
1378
- @result[:users] = []
1379
- when 'productCodes'
1380
- @result[:aws_product_codes] = []
1381
- end
1382
- end
1383
- def tagend(name)
1384
- # right now only 'launchPermission' is supported by Amazon.
1385
- # But nobody know what will they xml later as attribute. That is why we
1386
- # check for 'group' and 'userId' inside of 'launchPermission/item'
1387
- case name
1388
- when 'imageId' then @result[:aws_id] = @text
1389
- when 'group' then @result[:groups] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item'
1390
- when 'userId' then @result[:users] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item'
1391
- when 'productCode' then @result[:aws_product_codes] << @text
1392
- when 'kernel' then @result[:aws_kernel] = @text
1393
- when 'ramdisk' then @result[:aws_ramdisk] = @text
1394
- when 'blockDeviceMapping' then @result[:block_device_mapping] = @text
1395
- end
1396
- end
1397
- def reset
1398
- @result = {}
1399
- end
1400
- end
1401
-
1402
- #-----------------------------------------------------------------
1403
- # PARSERS: Instances
1404
- #-----------------------------------------------------------------
1405
-
1406
- class QEc2DescribeInstancesParser < RightAWSParser #:nodoc:
1407
- def tagstart(name, attributes)
1408
- # DescribeInstances property
1409
- if (name == 'item' && @xmlpath == 'DescribeInstancesResponse/reservationSet') ||
1410
- # RunInstances property
1411
- (name == 'RunInstancesResponse')
1412
- @reservation = { :aws_groups => [],
1413
- :instances_set => [] }
1414
-
1415
- elsif (name == 'item') &&
1416
- # DescribeInstances property
1417
- ( @xmlpath=='DescribeInstancesResponse/reservationSet/item/instancesSet' ||
1418
- # RunInstances property
1419
- @xmlpath=='RunInstancesResponse/instancesSet' )
1420
- # the optional params (sometimes are missing and we dont want them to be nil)
1421
- @instance = { :aws_reason => '',
1422
- :dns_name => '',
1423
- :private_dns_name => '',
1424
- :ami_launch_index => '',
1425
- :ssh_key_name => '',
1426
- :aws_state => '',
1427
- :aws_product_codes => [] }
1428
- end
1429
- end
1430
- def tagend(name)
1431
- case name
1432
- # reservation
1433
- when 'reservationId' then @reservation[:aws_reservation_id] = @text
1434
- when 'ownerId' then @reservation[:aws_owner] = @text
1435
- when 'groupId' then @reservation[:aws_groups] << @text
1436
- # instance
1437
- when 'instanceId' then @instance[:aws_instance_id] = @text
1438
- when 'imageId' then @instance[:aws_image_id] = @text
1439
- when 'dnsName' then @instance[:dns_name] = @text
1440
- when 'privateDnsName' then @instance[:private_dns_name] = @text
1441
- when 'reason' then @instance[:aws_reason] = @text
1442
- when 'keyName' then @instance[:ssh_key_name] = @text
1443
- when 'amiLaunchIndex' then @instance[:ami_launch_index] = @text
1444
- when 'code' then @instance[:aws_state_code] = @text
1445
- when 'name' then @instance[:aws_state] = @text
1446
- when 'productCode' then @instance[:aws_product_codes] << @text
1447
- when 'instanceType' then @instance[:aws_instance_type] = @text
1448
- when 'launchTime' then @instance[:aws_launch_time] = @text
1449
- when 'kernelId' then @instance[:aws_kernel_id] = @text
1450
- when 'ramdiskId' then @instance[:aws_ramdisk_id] = @text
1451
- when 'platform' then @instance[:aws_platform] = @text
1452
- when 'availabilityZone' then @instance[:aws_availability_zone] = @text
1453
- when 'item'
1454
- if @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet' || # DescribeInstances property
1455
- @xmlpath == 'RunInstancesResponse/instancesSet' # RunInstances property
1456
- @reservation[:instances_set] << @instance
1457
- elsif @xmlpath=='DescribeInstancesResponse/reservationSet' # DescribeInstances property
1458
- @result << @reservation
1459
- end
1460
- when 'RunInstancesResponse' then @result << @reservation # RunInstances property
1461
- end
1462
- end
1463
- def reset
1464
- @result = []
1465
- end
1466
- end
1467
-
1468
- class QEc2ConfirmProductInstanceParser < RightAWSParser #:nodoc:
1469
- def tagend(name)
1470
- @result = @text if name == 'ownerId'
1471
- end
1472
- end
1473
-
1474
- class QEc2TerminateInstancesParser < RightAWSParser #:nodoc:
1475
- def tagstart(name, attributes)
1476
- @instance = {} if name == 'item'
1477
- end
1478
- def tagend(name)
1479
- case name
1480
- when 'instanceId' then @instance[:aws_instance_id] = @text
1481
- when 'code'
1482
- if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState'
1483
- @instance[:aws_shutdown_state_code] = @text.to_i
1484
- else @instance[:aws_prev_state_code] = @text.to_i end
1485
- when 'name'
1486
- if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState'
1487
- @instance[:aws_shutdown_state] = @text
1488
- else @instance[:aws_prev_state] = @text end
1489
- when 'item' then @result << @instance
1490
- end
1491
- end
1492
- def reset
1493
- @result = []
1494
- end
1495
- end
1496
-
1497
- #-----------------------------------------------------------------
1498
- # PARSERS: Console
1499
- #-----------------------------------------------------------------
1500
-
1501
- class QEc2GetConsoleOutputParser < RightAWSParser #:nodoc:
1502
- def tagend(name)
1503
- case name
1504
- when 'instanceId' then @result[:aws_instance_id] = @text
1505
- when 'timestamp' then @result[:aws_timestamp] = @text
1506
- @result[:timestamp] = (Time.parse(@text)).utc
1507
- when 'output' then @result[:aws_output] = Base64.decode64(@text)
1508
- end
1509
- end
1510
- def reset
1511
- @result = {}
1512
- end
1513
- end
1514
-
1515
- #-----------------------------------------------------------------
1516
- # Instances: Wondows related part
1517
- #-----------------------------------------------------------------
1518
- class QEc2DescribeBundleTasksParser < RightAWSParser #:nodoc:
1519
- def tagstart(name, attributes)
1520
- @bundle = {} if name == 'item'
1521
- end
1522
- def tagend(name)
1523
- case name
1524
- # when 'requestId' then @bundle[:request_id] = @text
1525
- when 'instanceId' then @bundle[:aws_instance_id] = @text
1526
- when 'bundleId' then @bundle[:aws_id] = @text
1527
- when 'bucket' then @bundle[:s3_bucket] = @text
1528
- when 'prefix' then @bundle[:s3_prefix] = @text
1529
- when 'startTime' then @bundle[:aws_start_time] = @text
1530
- when 'updateTime' then @bundle[:aws_update_time] = @text
1531
- when 'state' then @bundle[:aws_state] = @text
1532
- when 'progress' then @bundle[:aws_progress] = @text
1533
- when 'code' then @bundle[:aws_error_code] = @text
1534
- when 'message' then @bundle[:aws_error_message] = @text
1535
- when 'item' then @result << @bundle
1536
- end
1537
- end
1538
- def reset
1539
- @result = []
1540
- end
1541
- end
1542
-
1543
- class QEc2BundleInstanceParser < RightAWSParser #:nodoc:
1544
- def tagend(name)
1545
- case name
1546
- # when 'requestId' then @result[:request_id] = @text
1547
- when 'instanceId' then @result[:aws_instance_id] = @text
1548
- when 'bundleId' then @result[:aws_id] = @text
1549
- when 'bucket' then @result[:s3_bucket] = @text
1550
- when 'prefix' then @result[:s3_prefix] = @text
1551
- when 'startTime' then @result[:aws_start_time] = @text
1552
- when 'updateTime' then @result[:aws_update_time] = @text
1553
- when 'state' then @result[:aws_state] = @text
1554
- when 'progress' then @result[:aws_progress] = @text
1555
- when 'code' then @result[:aws_error_code] = @text
1556
- when 'message' then @result[:aws_error_message] = @text
1557
- end
1558
- end
1559
- def reset
1560
- @result = {}
1561
- end
1562
- end
1563
-
1564
333
  #-----------------------------------------------------------------
1565
334
  # PARSERS: Elastic IPs
1566
335
  #-----------------------------------------------------------------
@@ -1600,7 +369,7 @@ module RightAws
1600
369
  when 'regionName' then @zone[:region_name] = @text
1601
370
  when 'zoneName' then @zone[:zone_name] = @text
1602
371
  when 'zoneState' then @zone[:zone_state] = @text
1603
- when 'item' then @result << @zone
372
+ when 'item' then @result << @zone
1604
373
  end
1605
374
  end
1606
375
  def reset
@@ -1620,117 +389,6 @@ module RightAws
1620
389
  @result = []
1621
390
  end
1622
391
  end
1623
-
1624
- #-----------------------------------------------------------------
1625
- # PARSERS: EBS - Volumes
1626
- #-----------------------------------------------------------------
1627
-
1628
- class QEc2CreateVolumeParser < RightAWSParser #:nodoc:
1629
- def tagend(name)
1630
- case name
1631
- when 'volumeId' then @result[:aws_id] = @text
1632
- when 'status' then @result[:aws_status] = @text
1633
- when 'createTime' then @result[:aws_created_at] = Time.parse(@text)
1634
- when 'size' then @result[:aws_size] = @text.to_i ###
1635
- when 'snapshotId' then @result[:snapshot_id] = @text.blank? ? nil : @text ###
1636
- when 'availabilityZone' then @result[:zone] = @text ###
1637
- end
1638
- end
1639
- def reset
1640
- @result = {}
1641
- end
1642
- end
1643
-
1644
- class QEc2AttachAndDetachVolumeParser < RightAWSParser #:nodoc:
1645
- def tagend(name)
1646
- case name
1647
- when 'volumeId' then @result[:aws_id] = @text
1648
- when 'instanceId' then @result[:aws_instance_id] = @text
1649
- when 'device' then @result[:aws_device] = @text
1650
- when 'status' then @result[:aws_attachment_status] = @text
1651
- when 'attachTime' then @result[:aws_attached_at] = Time.parse(@text)
1652
- end
1653
- end
1654
- def reset
1655
- @result = {}
1656
- end
1657
- end
1658
-
1659
- class QEc2DescribeVolumesParser < RightAWSParser #:nodoc:
1660
- def tagstart(name, attributes)
1661
- case name
1662
- when 'item'
1663
- case @xmlpath
1664
- when 'DescribeVolumesResponse/volumeSet' then @volume = {}
1665
- end
1666
- end
1667
- end
1668
- def tagend(name)
1669
- case name
1670
- when 'volumeId'
1671
- case @xmlpath
1672
- when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_id] = @text
1673
- end
1674
- when 'status'
1675
- case @xmlpath
1676
- when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_status] = @text
1677
- when 'DescribeVolumesResponse/volumeSet/item/attachmentSet/item' then @volume[:aws_attachment_status] = @text
1678
- end
1679
- when 'size' then @volume[:aws_size] = @text.to_i
1680
- when 'createTime' then @volume[:aws_created_at] = Time.parse(@text)
1681
- when 'instanceId' then @volume[:aws_instance_id] = @text
1682
- when 'device' then @volume[:aws_device] = @text
1683
- when 'attachTime' then @volume[:aws_attached_at] = Time.parse(@text)
1684
- when 'snapshotId' then @volume[:snapshot_id] = @text.blank? ? nil : @text
1685
- when 'availabilityZone' then @volume[:zone] = @text
1686
- when 'item'
1687
- case @xmlpath
1688
- when 'DescribeVolumesResponse/volumeSet' then @result << @volume
1689
- end
1690
- end
1691
- end
1692
- def reset
1693
- @result = []
1694
- end
1695
- end
1696
-
1697
- #-----------------------------------------------------------------
1698
- # PARSERS: EBS - Snapshots
1699
- #-----------------------------------------------------------------
1700
-
1701
- class QEc2DescribeSnapshotsParser < RightAWSParser #:nodoc:
1702
- def tagstart(name, attributes)
1703
- @snapshot = {} if name == 'item'
1704
- end
1705
- def tagend(name)
1706
- case name
1707
- when 'volumeId' then @snapshot[:aws_volume_id] = @text
1708
- when 'snapshotId' then @snapshot[:aws_id] = @text
1709
- when 'status' then @snapshot[:aws_status] = @text
1710
- when 'startTime' then @snapshot[:aws_started_at] = Time.parse(@text)
1711
- when 'progress' then @snapshot[:aws_progress] = @text
1712
- when 'item' then @result << @snapshot
1713
- end
1714
- end
1715
- def reset
1716
- @result = []
1717
- end
1718
- end
1719
-
1720
- class QEc2CreateSnapshotParser < RightAWSParser #:nodoc:
1721
- def tagend(name)
1722
- case name
1723
- when 'volumeId' then @result[:aws_volume_id] = @text
1724
- when 'snapshotId' then @result[:aws_id] = @text
1725
- when 'status' then @result[:aws_status] = @text
1726
- when 'startTime' then @result[:aws_started_at] = Time.parse(@text)
1727
- when 'progress' then @result[:aws_progress] = @text
1728
- end
1729
- end
1730
- def reset
1731
- @result = {}
1732
- end
1733
- end
1734
392
 
1735
393
  end
1736
394