kerryb-right_aws 1.7.6 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ec2/right_ec2.rb CHANGED
@@ -25,7 +25,8 @@ module RightAws
25
25
 
26
26
  # = RightAWS::EC2 -- RightScale Amazon EC2 interface
27
27
  # The RightAws::EC2 class provides a complete interface to Amazon's
28
- # Elastic Compute Cloud service.
28
+ # Elastic Compute Cloud service, as well as the associated EBS (Elastic Block
29
+ # Store).
29
30
  # For explanations of the semantics
30
31
  # of each call, please refer to Amazon's documentation at
31
32
  # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=87
@@ -67,7 +68,7 @@ module RightAws
67
68
  include RightAwsBaseInterface
68
69
 
69
70
  # Amazon EC2 API version being used
70
- API_VERSION = "2008-02-01"
71
+ API_VERSION = "2009-04-04"
71
72
  DEFAULT_HOST = "ec2.amazonaws.com"
72
73
  DEFAULT_PATH = '/'
73
74
  DEFAULT_PROTOCOL = 'https'
@@ -99,12 +100,18 @@ module RightAws
99
100
  # Create a new handle to an EC2 account. All handles share the same per process or per thread
100
101
  # HTTP connection to Amazon EC2. Each handle is for a specific account. The params have the
101
102
  # following options:
103
+ # * <tt>:endpoint_url</tt> a fully qualified url to Amazon API endpoint (this overwrites: :server, :port, :service, :protocol and :region). Example: 'https://eu-west-1.ec2.amazonaws.com/'
102
104
  # * <tt>:server</tt>: EC2 service host, default: DEFAULT_HOST
105
+ # * <tt>:region</tt>: EC2 region (North America by default)
103
106
  # * <tt>:port</tt>: EC2 service port, default: DEFAULT_PORT
104
107
  # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
105
108
  # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
106
109
  # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
107
110
  # * <tt>:signature_version</tt>: The signature version : '0' or '1'(default)
111
+ # * <tt>:cache</tt>: true/false: caching for: ec2_describe_images, describe_instances,
112
+ # describe_images_by_owner, describe_images_by_executable_by, describe_availability_zones,
113
+ # describe_security_groups, describe_key_pairs, describe_addresses,
114
+ # describe_volumes, describe_snapshots methods, default: false.
108
115
  #
109
116
  def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
110
117
  init({ :name => 'EC2',
@@ -115,31 +122,41 @@ module RightAws
115
122
  aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
116
123
  aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
117
124
  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']
118
131
  end
119
132
 
120
133
 
121
134
  def generate_request(action, params={}) #:nodoc:
122
- service_hash = {"Action" => action,
123
- "AWSAccessKeyId" => @aws_access_key_id,
124
- "Version" => @@api,
125
- # MODIFIED: from Time.now.utc.stf... for eucalyptus
126
- "Timestamp" => Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
127
- "SignatureVersion" => signature_version }
135
+ service_hash = {"Action" => action,
136
+ "AWSAccessKeyId" => @aws_access_key_id,
137
+ "Version" => @@api }
128
138
  service_hash.update(params)
129
- # prepare string to sight
130
- string_to_sign = case signature_version
131
- when '0' then service_hash["Action"] + service_hash["Timestamp"]
132
- when '1' then service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
133
- end
134
- service_hash.update('Signature' => AwsUtils::sign(@aws_secret_access_key, string_to_sign))
135
- request_params = service_hash.to_a.collect{|key,val| key + "=" + CGI::escape(val) }.join("&")
136
- request = Net::HTTP::Get.new("#{@params[:service]}?#{request_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
137
153
  # prepare output hash
138
154
  { :request => request,
139
155
  :server => @params[:server],
140
156
  :port => @params[:port],
141
157
  :protocol => @params[:protocol],
142
- :proxy => @params[:proxy] }
158
+ :proxy => @params[:proxy] }
159
+
143
160
  end
144
161
 
145
162
  # Sends request to Amazon and parses the response
@@ -150,22 +167,6 @@ module RightAws
150
167
  request_info_impl(thread[:ec2_connection], @@bench, request, parser)
151
168
  end
152
169
 
153
- def request_cache_or_info(method, link, parser_class, use_cache=true) #:nodoc:
154
- # We do not want to break the logic of parsing hence will use a dummy parser to process all the standart
155
- # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
156
- # If the caching is enabled and hit then throw AwsNoChange.
157
- # P.S. caching works for the whole images list only! (when the list param is blank) response, params = request_info(link, QEc2DummyParser.new)
158
- # check cache
159
- response, params = request_info(link, QEc2DummyParser.new)
160
- cache_hits?(method.to_sym, response.body) if use_cache
161
- parser = parser_class.new(:logger => @logger)
162
- @@bench.xml.add!{ parser.parse(response, params) }
163
- result = block_given? ? yield(parser) : parser.result
164
- # update parsed data
165
- update_cache(method.to_sym, :parsed => result) if use_cache
166
- result
167
- end
168
-
169
170
  def hash_params(prefix, list) #:nodoc:
170
171
  groups = {}
171
172
  list.each_index{|i| groups.update("#{prefix}.#{i+1}"=>list[i])} if list
@@ -176,11 +177,19 @@ module RightAws
176
177
  # Images
177
178
  #-----------------------------------------------------------------
178
179
 
179
- def ec2_describe_images(list, list_by='ImageId', image_type=nil) #:nodoc:
180
- request_hash = hash_params(list_by, list.to_a)
180
+ # params:
181
+ # { 'ImageId' => ['id1', ..., 'idN'],
182
+ # 'Owner' => ['self', ..., 'userN'],
183
+ # 'ExecutableBy' => ['self', 'all', ..., 'userN']
184
+ # }
185
+ def ec2_describe_images(params={}, image_type=nil, cache_for=nil) #:nodoc:
186
+ request_hash = {}
187
+ params.each do |list_by, list|
188
+ request_hash.merge! hash_params(list_by, list.to_a)
189
+ end
181
190
  request_hash['ImageType'] = image_type if image_type
182
191
  link = generate_request("DescribeImages", request_hash)
183
- request_cache_or_info :describe_images, link, QEc2DescribeImagesParser, (list.blank? && list_by == 'ImageId' && image_type.blank?)
192
+ request_cache_or_info cache_for, link, QEc2DescribeImagesParser, @@bench, cache_for
184
193
  rescue Exception
185
194
  on_exception
186
195
  end
@@ -211,7 +220,9 @@ module RightAws
211
220
  # :aws_image_type => "machine"}]
212
221
  #
213
222
  def describe_images(list=[], image_type=nil)
214
- ec2_describe_images(list, 'ImageId', image_type)
223
+ list = list.to_a
224
+ cache_for = list.empty? && !image_type ? :describe_images : nil
225
+ ec2_describe_images({ 'ImageId' => list }, image_type, cache_for)
215
226
  end
216
227
 
217
228
  #
@@ -220,8 +231,10 @@ module RightAws
220
231
  # ec2.describe_images_by_owner('522821470517')
221
232
  # ec2.describe_images_by_owner('self')
222
233
  #
223
- def describe_images_by_owner(list, image_type=nil)
224
- ec2_describe_images(list, 'Owner', image_type)
234
+ def describe_images_by_owner(list=['self'], image_type=nil)
235
+ list = list.to_a
236
+ cache_for = list==['self'] && !image_type ? :describe_images_by_owner : nil
237
+ ec2_describe_images({ 'Owner' => list }, image_type, cache_for)
225
238
  end
226
239
 
227
240
  #
@@ -229,9 +242,12 @@ module RightAws
229
242
  #
230
243
  # ec2.describe_images_by_executable_by('522821470517')
231
244
  # ec2.describe_images_by_executable_by('self')
245
+ # ec2.describe_images_by_executable_by('all')
232
246
  #
233
- def describe_images_by_executable_by(list, image_type=nil)
234
- ec2_describe_images(list, 'ExecutableBy', image_type)
247
+ def describe_images_by_executable_by(list=['self'], image_type=nil)
248
+ list = list.to_a
249
+ cache_for = list==['self'] && !image_type ? :describe_images_by_executable_by : nil
250
+ ec2_describe_images({ 'ExecutableBy' => list }, image_type, cache_for)
235
251
  end
236
252
 
237
253
 
@@ -399,7 +415,7 @@ module RightAws
399
415
  #
400
416
  def describe_instances(list=[])
401
417
  link = generate_request("DescribeInstances", hash_params('InstanceId',list.to_a))
402
- request_cache_or_info(:describe_instances, link, QEc2DescribeInstancesParser, list.blank?) do |parser|
418
+ request_cache_or_info(:describe_instances, link, QEc2DescribeInstancesParser, @@bench, list.blank?) do |parser|
403
419
  get_desc_instances(parser.result)
404
420
  end
405
421
  rescue Exception
@@ -413,7 +429,7 @@ module RightAws
413
429
  #
414
430
  def confirm_product_instance(instance, product_code)
415
431
  link = generate_request("ConfirmProductInstance", { 'ProductCode' => product_code,
416
- 'InstanceId' => instance })
432
+ 'InstanceId' => instance })
417
433
  request_info(link, QEc2ConfirmProductInstanceParser.new(:logger => @logger))
418
434
  end
419
435
 
@@ -519,7 +535,7 @@ module RightAws
519
535
  # Amazon 169.254.169.254 does not like escaped symbols!
520
536
  # And it doesn't like "\n" inside of encoded string! Grrr....
521
537
  # Otherwise, some of UserData symbols will be lost...
522
- params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n") unless lparams[:user_data].blank?
538
+ params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n").strip unless lparams[:user_data].blank?
523
539
  end
524
540
  link = generate_request("RunInstances", params)
525
541
  #debugger
@@ -574,7 +590,127 @@ module RightAws
574
590
  rescue Exception
575
591
  on_exception
576
592
  end
593
+
594
+ #-----------------------------------------------------------------
595
+ # Instances: Windows addons
596
+ #-----------------------------------------------------------------
597
+
598
+ # Get initial Windows Server setup password from an instance console output.
599
+ #
600
+ # my_awesome_key = ec2.create_key_pair('my_awesome_key') #=>
601
+ # {:aws_key_name => "my_awesome_key",
602
+ # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03",
603
+ # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"}
604
+ #
605
+ # my_awesome_instance = ec2.run_instances('ami-a000000a',1,1,['my_awesome_group'],'my_awesome_key', 'WindowsInstance!!!') #=>
606
+ # [{:aws_image_id => "ami-a000000a",
607
+ # :aws_instance_id => "i-12345678",
608
+ # ...
609
+ # :aws_availability_zone => "us-east-1b"
610
+ # }]
611
+ #
612
+ # # wait until instance enters 'operational' state and get it's initial password
613
+ #
614
+ # puts ec2.get_initial_password(my_awesome_instance[:aws_instance_id], my_awesome_key[:aws_material]) #=> "MhjWcgZuY6"
615
+ #
616
+ def get_initial_password(instance_id, private_key)
617
+ console_output = get_console_output(instance_id)
618
+ crypted_password = console_output[:aws_output][%r{<Password>(.+)</Password>}m] && $1
619
+ unless crypted_password
620
+ raise AwsError.new("Initial password was not found in console output for #{instance_id}")
621
+ else
622
+ OpenSSL::PKey::RSA.new(private_key).private_decrypt(Base64.decode64(crypted_password))
623
+ end
624
+ rescue Exception
625
+ on_exception
626
+ end
627
+
628
+ # Bundle a Windows image.
629
+ # Internally, it queues the bundling task and shuts down the instance.
630
+ # It then takes a snapshot of the Windows volume bundles it, and uploads it to
631
+ # S3. After bundling completes, Rightaws::Ec2#register_image may be used to
632
+ # register the new Windows AMI for subsequent launches.
633
+ #
634
+ # ec2.bundle_instance('i-e3e24e8a', 'my-awesome-bucket', 'my-win-image-1') #=>
635
+ # [{:aws_update_time => "2008-10-16T13:58:25.000Z",
636
+ # :s3_bucket => "kd-win-1",
637
+ # :s3_prefix => "win2pr",
638
+ # :aws_state => "pending",
639
+ # :aws_id => "bun-26a7424f",
640
+ # :aws_instance_id => "i-878a25ee",
641
+ # :aws_start_time => "2008-10-16T13:58:02.000Z"}]
642
+ #
643
+ def bundle_instance(instance_id, s3_bucket, s3_prefix,
644
+ s3_owner_aws_access_key_id=nil, s3_owner_aws_secret_access_key=nil,
645
+ s3_expires = S3Interface::DEFAULT_EXPIRES_AFTER,
646
+ s3_upload_policy='ec2-bundle-read')
647
+ # S3 access and signatures
648
+ s3_owner_aws_access_key_id ||= @aws_access_key_id
649
+ s3_owner_aws_secret_access_key ||= @aws_secret_access_key
650
+ s3_expires = Time.now.utc + s3_expires if s3_expires.is_a?(Fixnum) && (s3_expires < S3Interface::ONE_YEAR_IN_SECONDS)
651
+ # policy
652
+ policy = { 'expiration' => s3_expires.strftime('%Y-%m-%dT%H:%M:%SZ'),
653
+ 'conditions' => [ { 'bucket' => s3_bucket },
654
+ { 'acl' => s3_upload_policy },
655
+ [ 'starts-with', '$key', s3_prefix ] ] }.to_json
656
+ policy64 = Base64.encode64(policy).gsub("\n","")
657
+ signed_policy64 = AwsUtils.sign(s3_owner_aws_secret_access_key, policy64)
658
+ # fill request params
659
+ params = { 'InstanceId' => instance_id,
660
+ 'Storage.S3.AWSAccessKeyId' => s3_owner_aws_access_key_id,
661
+ 'Storage.S3.UploadPolicy' => policy64,
662
+ 'Storage.S3.UploadPolicySignature' => signed_policy64,
663
+ 'Storage.S3.Bucket' => s3_bucket,
664
+ 'Storage.S3.Prefix' => s3_prefix,
665
+ }
666
+ link = generate_request("BundleInstance", params)
667
+ request_info(link, QEc2BundleInstanceParser.new)
668
+ rescue Exception
669
+ on_exception
670
+ end
577
671
 
672
+ # Describe the status of the Windows AMI bundlings.
673
+ # If +list+ is omitted the returns the whole list of tasks.
674
+ #
675
+ # ec2.describe_bundle_tasks(['bun-4fa74226']) #=>
676
+ # [{:s3_bucket => "my-awesome-bucket"
677
+ # :aws_id => "bun-0fa70206",
678
+ # :s3_prefix => "win1pr",
679
+ # :aws_start_time => "2008-10-14T16:27:57.000Z",
680
+ # :aws_update_time => "2008-10-14T16:37:10.000Z",
681
+ # :aws_error_code => "Client.S3Error",
682
+ # :aws_error_message =>
683
+ # "AccessDenied(403)- Invalid according to Policy: Policy Condition failed: [\"eq\", \"$acl\", \"aws-exec-read\"]",
684
+ # :aws_state => "failed",
685
+ # :aws_instance_id => "i-e3e24e8a"}]
686
+ #
687
+ def describe_bundle_tasks(list=[])
688
+ link = generate_request("DescribeBundleTasks", hash_params('BundleId', list.to_a))
689
+ request_info(link, QEc2DescribeBundleTasksParser.new)
690
+ rescue Exception
691
+ on_exception
692
+ end
693
+
694
+ # Cancel an in‐progress or pending bundle task by id.
695
+ #
696
+ # ec2.cancel_bundle_task('bun-73a7421a') #=>
697
+ # [{:s3_bucket => "my-awesome-bucket"
698
+ # :aws_id => "bun-0fa70206",
699
+ # :s3_prefix => "win02",
700
+ # :aws_start_time => "2008-10-14T13:00:29.000Z",
701
+ # :aws_error_message => "User has requested bundling operation cancellation",
702
+ # :aws_state => "failed",
703
+ # :aws_update_time => "2008-10-14T13:01:31.000Z",
704
+ # :aws_error_code => "Client.Cancelled",
705
+ # :aws_instance_id => "i-e3e24e8a"}
706
+ #
707
+ def cancel_bundle_task(bundle_id)
708
+ link = generate_request("CancelBundleTask", { 'BundleId' => bundle_id })
709
+ request_info(link, QEc2BundleInstanceParser.new)
710
+ rescue Exception
711
+ on_exception
712
+ end
713
+
578
714
  #-----------------------------------------------------------------
579
715
  # Security groups
580
716
  #-----------------------------------------------------------------
@@ -596,7 +732,7 @@ module RightAws
596
732
  #
597
733
  def describe_security_groups(list=[])
598
734
  link = generate_request("DescribeSecurityGroups", hash_params('GroupName',list.to_a))
599
- request_cache_or_info( :describe_security_groups, link, QEc2DescribeSecurityGroupsParser, list.blank?) do |parser|
735
+ request_cache_or_info( :describe_security_groups, link, QEc2DescribeSecurityGroupsParser, @@bench, list.blank?) do |parser|
600
736
  result = []
601
737
  parser.result.each do |item|
602
738
  perms = []
@@ -641,7 +777,7 @@ module RightAws
641
777
  # EC2 doesn't like an empty description...
642
778
  description = " " if description.blank?
643
779
  link = generate_request("CreateSecurityGroup",
644
- 'GroupName' => name.to_s,
780
+ 'GroupName' => name.to_s,
645
781
  'GroupDescription' => description.to_s)
646
782
  request_info(link, RightBoolResponseParser.new(:logger => @logger))
647
783
  rescue Exception
@@ -667,8 +803,8 @@ module RightAws
667
803
  #
668
804
  def authorize_security_group_named_ingress(name, owner, group)
669
805
  link = generate_request("AuthorizeSecurityGroupIngress",
670
- 'GroupName' => name.to_s,
671
- 'SourceSecurityGroupName' => group.to_s,
806
+ 'GroupName' => name.to_s,
807
+ 'SourceSecurityGroupName' => group.to_s,
672
808
  'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
673
809
  request_info(link, RightBoolResponseParser.new(:logger => @logger))
674
810
  rescue Exception
@@ -681,8 +817,8 @@ module RightAws
681
817
  #
682
818
  def revoke_security_group_named_ingress(name, owner, group)
683
819
  link = generate_request("RevokeSecurityGroupIngress",
684
- 'GroupName' => name.to_s,
685
- 'SourceSecurityGroupName' => group.to_s,
820
+ 'GroupName' => name.to_s,
821
+ 'SourceSecurityGroupName' => group.to_s,
686
822
  'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
687
823
  request_info(link, RightBoolResponseParser.new(:logger => @logger))
688
824
  rescue Exception
@@ -696,10 +832,10 @@ module RightAws
696
832
  #
697
833
  def authorize_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
698
834
  link = generate_request("AuthorizeSecurityGroupIngress",
699
- 'GroupName' => name.to_s,
700
- 'IpProtocol' => protocol.to_s,
701
- 'FromPort' => from_port.to_s,
702
- 'ToPort' => to_port.to_s,
835
+ 'GroupName' => name.to_s,
836
+ 'IpProtocol' => protocol.to_s,
837
+ 'FromPort' => from_port.to_s,
838
+ 'ToPort' => to_port.to_s,
703
839
  'CidrIp' => cidr_ip.to_s)
704
840
  request_info(link, RightBoolResponseParser.new(:logger => @logger))
705
841
  rescue Exception
@@ -712,10 +848,10 @@ module RightAws
712
848
  #
713
849
  def revoke_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
714
850
  link = generate_request("RevokeSecurityGroupIngress",
715
- 'GroupName' => name.to_s,
716
- 'IpProtocol' => protocol.to_s,
717
- 'FromPort' => from_port.to_s,
718
- 'ToPort' => to_port.to_s,
851
+ 'GroupName' => name.to_s,
852
+ 'IpProtocol' => protocol.to_s,
853
+ 'FromPort' => from_port.to_s,
854
+ 'ToPort' => to_port.to_s,
719
855
  'CidrIp' => cidr_ip.to_s)
720
856
  request_info(link, RightBoolResponseParser.new(:logger => @logger))
721
857
  rescue Exception
@@ -736,7 +872,7 @@ module RightAws
736
872
  #
737
873
  def describe_key_pairs(list=[])
738
874
  link = generate_request("DescribeKeyPairs", hash_params('KeyName',list.to_a))
739
- request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, list.blank?
875
+ request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, @@bench, list.blank?
740
876
  rescue Exception
741
877
  on_exception
742
878
  end
@@ -755,7 +891,7 @@ module RightAws
755
891
  rescue Exception
756
892
  on_exception
757
893
  end
758
-
894
+
759
895
  # Delete a key pair. Returns +true+ or an exception.
760
896
  #
761
897
  # ec2.delete_key_pair('my_awesome_key') #=> true
@@ -809,7 +945,7 @@ module RightAws
809
945
  def describe_addresses(list=[])
810
946
  link = generate_request("DescribeAddresses",
811
947
  hash_params('PublicIp',list.to_a))
812
- request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, list.blank?
948
+ request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, @@bench, list.blank?
813
949
  rescue Exception
814
950
  on_exception
815
951
  end
@@ -847,21 +983,242 @@ module RightAws
847
983
  # Describes availability zones that are currently available to the account and their states.
848
984
  # Returns an array of 2 keys (:zone_name and :zone_state) hashes:
849
985
  #
850
- # ec2.describe_availability_zones #=> [{:zone_state=>"available", :zone_name=>"us-east-1a"},
851
- # {:zone_state=>"available", :zone_name=>"us-east-1b"},
852
- # {:zone_state=>"available", :zone_name=>"us-east-1c"}]
986
+ # ec2.describe_availability_zones #=> [{:region_name=>"us-east-1",
987
+ # :zone_name=>"us-east-1a",
988
+ # :zone_state=>"available"}, ... ]
853
989
  #
854
- # ec2.describe_availability_zones('us-east-1c') #=> [{:zone_state=>"available", :zone_name=>"us-east-1c"}]
990
+ # ec2.describe_availability_zones('us-east-1c') #=> [{:region_name=>"us-east-1",
991
+ # :zone_state=>"available",
992
+ # :zone_name=>"us-east-1c"}]
855
993
  #
856
994
  def describe_availability_zones(list=[])
857
995
  link = generate_request("DescribeAvailabilityZones",
858
996
  hash_params('ZoneName',list.to_a))
859
- request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, list.blank?
997
+ request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, @@bench, list.blank?
860
998
  rescue Exception
861
999
  on_exception
862
1000
  end
863
1001
 
1002
+ #-----------------------------------------------------------------
1003
+ # Regions
1004
+ #-----------------------------------------------------------------
1005
+
1006
+ # Describe regions.
1007
+ #
1008
+ # ec2.describe_regions #=> ["eu-west-1", "us-east-1"]
1009
+ #
1010
+ def describe_regions(list=[])
1011
+ link = generate_request("DescribeRegions",
1012
+ hash_params('RegionName',list.to_a))
1013
+ request_cache_or_info :describe_regions, link, QEc2DescribeRegionsParser, @@bench, list.blank?
1014
+ rescue Exception
1015
+ on_exception
1016
+ end
1017
+
1018
+
1019
+ #-----------------------------------------------------------------
1020
+ # EBS: Volumes
1021
+ #-----------------------------------------------------------------
864
1022
 
1023
+ # Describe all EBS volumes.
1024
+ #
1025
+ # ec2.describe_volumes #=>
1026
+ # [{:aws_size => 94,
1027
+ # :aws_device => "/dev/sdc",
1028
+ # :aws_attachment_status => "attached",
1029
+ # :zone => "merlot",
1030
+ # :snapshot_id => nil,
1031
+ # :aws_attached_at => Wed Jun 18 08:19:28 UTC 2008,
1032
+ # :aws_status => "in-use",
1033
+ # :aws_id => "vol-60957009",
1034
+ # :aws_created_at => Wed Jun 18 08:19:20s UTC 2008,
1035
+ # :aws_instance_id => "i-c014c0a9"},
1036
+ # {:aws_size => 1,
1037
+ # :zone => "merlot",
1038
+ # :snapshot_id => nil,
1039
+ # :aws_status => "available",
1040
+ # :aws_id => "vol-58957031",
1041
+ # :aws_created_at => Wed Jun 18 08:19:21 UTC 2008,}, ... ]
1042
+ #
1043
+ def describe_volumes(list=[])
1044
+ link = generate_request("DescribeVolumes",
1045
+ hash_params('VolumeId',list.to_a))
1046
+ request_cache_or_info :describe_volumes, link, QEc2DescribeVolumesParser, @@bench, list.blank?
1047
+ rescue Exception
1048
+ on_exception
1049
+ end
1050
+
1051
+ # Create new EBS volume based on previously created snapshot.
1052
+ # +Size+ in Gigabytes.
1053
+ #
1054
+ # ec2.create_volume('snap-000000', 10, zone) #=>
1055
+ # {:snapshot_id => "snap-e21df98b",
1056
+ # :aws_status => "creating",
1057
+ # :aws_id => "vol-fc9f7a95",
1058
+ # :zone => "merlot",
1059
+ # :aws_created_at => Tue Jun 24 18:13:32 UTC 2008,
1060
+ # :aws_size => 94}
1061
+ #
1062
+ def create_volume(zone, snapshot_id, size)
1063
+ hash = {"AvailabilityZone" => zone.to_s}
1064
+ hash["SnapshotId"] = snapshot_id.to_s unless snapshot_id.blank?
1065
+ hash["Size"] = size.to_s unless size.blank?
1066
+
1067
+ link = generate_request("CreateVolume", hash)
1068
+
1069
+ request_info(link, QEc2CreateVolumeParser.new(:logger => @logger))
1070
+ rescue Exception
1071
+ on_exception
1072
+ end
1073
+
1074
+ # Delete the specified EBS volume.
1075
+ # This does not deletes any snapshots created from this volume.
1076
+ #
1077
+ # ec2.delete_volume('vol-b48a6fdd') #=> true
1078
+ #
1079
+ def delete_volume(volume_id)
1080
+ link = generate_request("DeleteVolume",
1081
+ "VolumeId" => volume_id.to_s)
1082
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
1083
+ rescue Exception
1084
+ on_exception
1085
+ end
1086
+
1087
+ # Attach the specified EBS volume to a specified instance, exposing the
1088
+ # volume using the specified device name.
1089
+ #
1090
+ # ec2.attach_volume('vol-898a6fe0', 'i-7c905415', '/dev/sdh') #=>
1091
+ # { :aws_instance_id => "i-7c905415",
1092
+ # :aws_device => "/dev/sdh",
1093
+ # :aws_status => "attaching",
1094
+ # :aws_attached_at => "2008-03-28T14:14:39.000Z",
1095
+ # :aws_id => "vol-898a6fe0" }
1096
+ #
1097
+ def attach_volume(volume_id, instance_id, device)
1098
+ link = generate_request("AttachVolume",
1099
+ "VolumeId" => volume_id.to_s,
1100
+ "InstanceId" => instance_id.to_s,
1101
+ "Device" => device.to_s)
1102
+ request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger))
1103
+ rescue Exception
1104
+ on_exception
1105
+ end
1106
+
1107
+ # Detach the specified EBS volume from the instance to which it is attached.
1108
+ #
1109
+ # ec2.detach_volume('vol-898a6fe0') #=>
1110
+ # { :aws_instance_id => "i-7c905415",
1111
+ # :aws_device => "/dev/sdh",
1112
+ # :aws_status => "detaching",
1113
+ # :aws_attached_at => "2008-03-28T14:38:34.000Z",
1114
+ # :aws_id => "vol-898a6fe0"}
1115
+ #
1116
+ def detach_volume(volume_id, instance_id=nil, device=nil, force=nil)
1117
+ hash = { "VolumeId" => volume_id.to_s }
1118
+ hash["InstanceId"] = instance_id.to_s unless instance_id.blank?
1119
+ hash["Device"] = device.to_s unless device.blank?
1120
+ hash["Force"] = 'true' if force
1121
+ #
1122
+ link = generate_request("DetachVolume", hash)
1123
+ request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger))
1124
+ rescue Exception
1125
+ on_exception
1126
+ end
1127
+
1128
+
1129
+ #-----------------------------------------------------------------
1130
+ # EBS: Snapshots
1131
+ #-----------------------------------------------------------------
1132
+
1133
+ # Describe all EBS snapshots.
1134
+ #
1135
+ # ec2.describe_snapshots #=>
1136
+ # [ { :aws_progress => "100%",
1137
+ # :aws_status => "completed",
1138
+ # :aws_id => "snap-72a5401b",
1139
+ # :aws_volume_id => "vol-5582673c",
1140
+ # :aws_started_at => "2008-02-23T02:50:48.000Z"},
1141
+ # { :aws_progress => "100%",
1142
+ # :aws_status => "completed",
1143
+ # :aws_id => "snap-75a5401c",
1144
+ # :aws_volume_id => "vol-5582673c",
1145
+ # :aws_started_at => "2008-02-23T16:23:19.000Z" },...]
1146
+ #
1147
+ def describe_snapshots(list=[])
1148
+ link = generate_request("DescribeSnapshots",
1149
+ hash_params('SnapshotId',list.to_a))
1150
+ request_cache_or_info :describe_snapshots, link, QEc2DescribeSnapshotsParser, @@bench, list.blank?
1151
+ rescue Exception
1152
+ on_exception
1153
+ end
1154
+
1155
+ # Create a snapshot of specified volume.
1156
+ #
1157
+ # ec2.create_snapshot('vol-898a6fe0') #=>
1158
+ # {:aws_volume_id => "vol-fd9f7a94",
1159
+ # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1160
+ # :aws_progress => "",
1161
+ # :aws_status => "pending",
1162
+ # :aws_id => "snap-d56783bc"}
1163
+ #
1164
+ def create_snapshot(volume_id)
1165
+ link = generate_request("CreateSnapshot",
1166
+ "VolumeId" => volume_id.to_s)
1167
+ request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1168
+ rescue Exception
1169
+ on_exception
1170
+ end
1171
+
1172
+ # Create a snapshot of specified volume, but with the normal retry algorithms disabled.
1173
+ # This method will return immediately upon error. The user can specify connect and read timeouts (in s)
1174
+ # for the connection to AWS. If the user does not specify timeouts, try_create_snapshot uses the default values
1175
+ # in Rightscale::HttpConnection.
1176
+ #
1177
+ # ec2.try_create_snapshot('vol-898a6fe0') #=>
1178
+ # {:aws_volume_id => "vol-fd9f7a94",
1179
+ # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1180
+ # :aws_progress => "",
1181
+ # :aws_status => "pending",
1182
+ # :aws_id => "snap-d56783bc"}
1183
+ #
1184
+ def try_create_snapshot(volume_id, connect_timeout = nil, read_timeout = nil)
1185
+ # For safety in the ensure block...we don't want to restore values
1186
+ # if we never read them in the first place
1187
+ orig_reiteration_time = nil
1188
+ orig_http_params = nil
1189
+
1190
+ orig_reiteration_time = RightAws::AWSErrorHandler::reiteration_time
1191
+ RightAws::AWSErrorHandler::reiteration_time = 0
1192
+
1193
+ orig_http_params = Rightscale::HttpConnection::params()
1194
+ new_http_params = orig_http_params.dup
1195
+ new_http_params[:http_connection_retry_count] = 0
1196
+ new_http_params[:http_connection_open_timeout] = connect_timeout if !connect_timeout.nil?
1197
+ new_http_params[:http_connection_read_timeout] = read_timeout if !read_timeout.nil?
1198
+ Rightscale::HttpConnection::params = new_http_params
1199
+
1200
+ link = generate_request("CreateSnapshot",
1201
+ "VolumeId" => volume_id.to_s)
1202
+ request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1203
+
1204
+ rescue Exception
1205
+ on_exception
1206
+ ensure
1207
+ RightAws::AWSErrorHandler::reiteration_time = orig_reiteration_time if orig_reiteration_time
1208
+ Rightscale::HttpConnection::params = orig_http_params if orig_http_params
1209
+ end
1210
+
1211
+ # Delete the specified snapshot.
1212
+ #
1213
+ # ec2.delete_snapshot('snap-55a5403c') #=> true
1214
+ #
1215
+ def delete_snapshot(snapshot_id)
1216
+ link = generate_request("DeleteSnapshot",
1217
+ "SnapshotId" => snapshot_id.to_s)
1218
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
1219
+ rescue Exception
1220
+ on_exception
1221
+ end
865
1222
 
866
1223
  #-----------------------------------------------------------------
867
1224
  # PARSERS: Boolean Response Parser
@@ -1000,6 +1357,7 @@ module RightAws
1000
1357
  when 'kernelId' then @image[:aws_kernel_id] = @text
1001
1358
  when 'ramdiskId' then @image[:aws_ramdisk_id] = @text
1002
1359
  when 'item' then @result << @image if @xmlpath[%r{.*/imagesSet$}]
1360
+ when 'platform' then @image[:platform] = @text
1003
1361
  end
1004
1362
  end
1005
1363
  def reset
@@ -1095,6 +1453,7 @@ module RightAws
1095
1453
  when 'launchTime' then @instance[:aws_launch_time] = @text
1096
1454
  when 'kernelId' then @instance[:aws_kernel_id] = @text
1097
1455
  when 'ramdiskId' then @instance[:aws_ramdisk_id] = @text
1456
+ when 'platform' then @instance[:aws_platform] = @text
1098
1457
  when 'availabilityZone' then @instance[:aws_availability_zone] = @text
1099
1458
  when 'item'
1100
1459
  if @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet' || # DescribeInstances property
@@ -1159,18 +1518,54 @@ module RightAws
1159
1518
  end
1160
1519
 
1161
1520
  #-----------------------------------------------------------------
1162
- # PARSERS: Fake
1521
+ # Instances: Wondows related part
1163
1522
  #-----------------------------------------------------------------
1164
-
1165
- # Dummy parser - does nothing
1166
- # Returns the original params back
1167
- class QEc2DummyParser # :nodoc:
1168
- attr_accessor :result
1169
- def parse(response, params={})
1170
- @result = [response, params]
1523
+ class QEc2DescribeBundleTasksParser < RightAWSParser #:nodoc:
1524
+ def tagstart(name, attributes)
1525
+ @bundle = {} if name == 'item'
1526
+ end
1527
+ def tagend(name)
1528
+ case name
1529
+ # when 'requestId' then @bundle[:request_id] = @text
1530
+ when 'instanceId' then @bundle[:aws_instance_id] = @text
1531
+ when 'bundleId' then @bundle[:aws_id] = @text
1532
+ when 'bucket' then @bundle[:s3_bucket] = @text
1533
+ when 'prefix' then @bundle[:s3_prefix] = @text
1534
+ when 'startTime' then @bundle[:aws_start_time] = @text
1535
+ when 'updateTime' then @bundle[:aws_update_time] = @text
1536
+ when 'state' then @bundle[:aws_state] = @text
1537
+ when 'progress' then @bundle[:aws_progress] = @text
1538
+ when 'code' then @bundle[:aws_error_code] = @text
1539
+ when 'message' then @bundle[:aws_error_message] = @text
1540
+ when 'item' then @result << @bundle
1541
+ end
1542
+ end
1543
+ def reset
1544
+ @result = []
1171
1545
  end
1172
1546
  end
1173
-
1547
+
1548
+ class QEc2BundleInstanceParser < RightAWSParser #:nodoc:
1549
+ def tagend(name)
1550
+ case name
1551
+ # when 'requestId' then @result[:request_id] = @text
1552
+ when 'instanceId' then @result[:aws_instance_id] = @text
1553
+ when 'bundleId' then @result[:aws_id] = @text
1554
+ when 'bucket' then @result[:s3_bucket] = @text
1555
+ when 'prefix' then @result[:s3_prefix] = @text
1556
+ when 'startTime' then @result[:aws_start_time] = @text
1557
+ when 'updateTime' then @result[:aws_update_time] = @text
1558
+ when 'state' then @result[:aws_state] = @text
1559
+ when 'progress' then @result[:aws_progress] = @text
1560
+ when 'code' then @result[:aws_error_code] = @text
1561
+ when 'message' then @result[:aws_error_message] = @text
1562
+ end
1563
+ end
1564
+ def reset
1565
+ @result = {}
1566
+ end
1567
+ end
1568
+
1174
1569
  #-----------------------------------------------------------------
1175
1570
  # PARSERS: Elastic IPs
1176
1571
  #-----------------------------------------------------------------
@@ -1207,8 +1602,9 @@ module RightAws
1207
1602
  end
1208
1603
  def tagend(name)
1209
1604
  case name
1210
- when 'zoneName' then @zone[:zone_name] = @text
1211
- when 'zoneState' then @zone[:zone_state] = @text
1605
+ when 'regionName' then @zone[:region_name] = @text
1606
+ when 'zoneName' then @zone[:zone_name] = @text
1607
+ when 'zoneState' then @zone[:zone_state] = @text
1212
1608
  when 'item' then @result << @zone
1213
1609
  end
1214
1610
  end
@@ -1217,7 +1613,129 @@ module RightAws
1217
1613
  end
1218
1614
  end
1219
1615
 
1616
+ #-----------------------------------------------------------------
1617
+ # PARSERS: Regions
1618
+ #-----------------------------------------------------------------
1619
+
1620
+ class QEc2DescribeRegionsParser < RightAWSParser #:nodoc:
1621
+ def tagend(name)
1622
+ @result << @text if name == 'regionName'
1623
+ end
1624
+ def reset
1625
+ @result = []
1626
+ end
1627
+ end
1628
+
1629
+ #-----------------------------------------------------------------
1630
+ # PARSERS: EBS - Volumes
1631
+ #-----------------------------------------------------------------
1220
1632
 
1633
+ class QEc2CreateVolumeParser < RightAWSParser #:nodoc:
1634
+ def tagend(name)
1635
+ case name
1636
+ when 'volumeId' then @result[:aws_id] = @text
1637
+ when 'status' then @result[:aws_status] = @text
1638
+ when 'createTime' then @result[:aws_created_at] = Time.parse(@text)
1639
+ when 'size' then @result[:aws_size] = @text.to_i ###
1640
+ when 'snapshotId' then @result[:snapshot_id] = @text.blank? ? nil : @text ###
1641
+ when 'availabilityZone' then @result[:zone] = @text ###
1642
+ end
1643
+ end
1644
+ def reset
1645
+ @result = {}
1646
+ end
1647
+ end
1648
+
1649
+ class QEc2AttachAndDetachVolumeParser < RightAWSParser #:nodoc:
1650
+ def tagend(name)
1651
+ case name
1652
+ when 'volumeId' then @result[:aws_id] = @text
1653
+ when 'instanceId' then @result[:aws_instance_id] = @text
1654
+ when 'device' then @result[:aws_device] = @text
1655
+ when 'status' then @result[:aws_attachment_status] = @text
1656
+ when 'attachTime' then @result[:aws_attached_at] = Time.parse(@text)
1657
+ end
1658
+ end
1659
+ def reset
1660
+ @result = {}
1661
+ end
1662
+ end
1663
+
1664
+ class QEc2DescribeVolumesParser < RightAWSParser #:nodoc:
1665
+ def tagstart(name, attributes)
1666
+ case name
1667
+ when 'item'
1668
+ case @xmlpath
1669
+ when 'DescribeVolumesResponse/volumeSet' then @volume = {}
1670
+ end
1671
+ end
1672
+ end
1673
+ def tagend(name)
1674
+ case name
1675
+ when 'volumeId'
1676
+ case @xmlpath
1677
+ when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_id] = @text
1678
+ end
1679
+ when 'status'
1680
+ case @xmlpath
1681
+ when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_status] = @text
1682
+ when 'DescribeVolumesResponse/volumeSet/item/attachmentSet/item' then @volume[:aws_attachment_status] = @text
1683
+ end
1684
+ when 'size' then @volume[:aws_size] = @text.to_i
1685
+ when 'createTime' then @volume[:aws_created_at] = Time.parse(@text)
1686
+ when 'instanceId' then @volume[:aws_instance_id] = @text
1687
+ when 'device' then @volume[:aws_device] = @text
1688
+ when 'attachTime' then @volume[:aws_attached_at] = Time.parse(@text)
1689
+ when 'snapshotId' then @volume[:snapshot_id] = @text.blank? ? nil : @text
1690
+ when 'availabilityZone' then @volume[:zone] = @text
1691
+ when 'item'
1692
+ case @xmlpath
1693
+ when 'DescribeVolumesResponse/volumeSet' then @result << @volume
1694
+ end
1695
+ end
1696
+ end
1697
+ def reset
1698
+ @result = []
1699
+ end
1700
+ end
1701
+
1702
+ #-----------------------------------------------------------------
1703
+ # PARSERS: EBS - Snapshots
1704
+ #-----------------------------------------------------------------
1705
+
1706
+ class QEc2DescribeSnapshotsParser < RightAWSParser #:nodoc:
1707
+ def tagstart(name, attributes)
1708
+ @snapshot = {} if name == 'item'
1709
+ end
1710
+ def tagend(name)
1711
+ case name
1712
+ when 'volumeId' then @snapshot[:aws_volume_id] = @text
1713
+ when 'snapshotId' then @snapshot[:aws_id] = @text
1714
+ when 'status' then @snapshot[:aws_status] = @text
1715
+ when 'startTime' then @snapshot[:aws_started_at] = Time.parse(@text)
1716
+ when 'progress' then @snapshot[:aws_progress] = @text
1717
+ when 'item' then @result << @snapshot
1718
+ end
1719
+ end
1720
+ def reset
1721
+ @result = []
1722
+ end
1723
+ end
1724
+
1725
+ class QEc2CreateSnapshotParser < RightAWSParser #:nodoc:
1726
+ def tagend(name)
1727
+ case name
1728
+ when 'volumeId' then @result[:aws_volume_id] = @text
1729
+ when 'snapshotId' then @result[:aws_id] = @text
1730
+ when 'status' then @result[:aws_status] = @text
1731
+ when 'startTime' then @result[:aws_started_at] = Time.parse(@text)
1732
+ when 'progress' then @result[:aws_progress] = @text
1733
+ end
1734
+ end
1735
+ def reset
1736
+ @result = {}
1737
+ end
1738
+ end
1221
1739
 
1222
1740
  end
1223
1741