icehouse-right_aws 1.11.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/History.txt +93 -15
  2. data/Manifest.txt +15 -1
  3. data/README.txt +0 -4
  4. data/Rakefile +34 -17
  5. data/lib/acf/right_acf_interface.rb +260 -124
  6. data/lib/acf/right_acf_invalidations.rb +144 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +229 -0
  9. data/lib/acw/right_acw_interface.rb +4 -5
  10. data/lib/as/right_as_interface.rb +59 -51
  11. data/lib/awsbase/benchmark_fix.rb +0 -0
  12. data/lib/awsbase/right_awsbase.rb +351 -104
  13. data/lib/awsbase/support.rb +2 -82
  14. data/lib/awsbase/version.rb +9 -0
  15. data/lib/ec2/right_ec2.rb +97 -246
  16. data/lib/ec2/right_ec2_ebs.rb +88 -68
  17. data/lib/ec2/right_ec2_images.rb +90 -50
  18. data/lib/ec2/right_ec2_instances.rb +118 -89
  19. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  20. data/lib/ec2/right_ec2_reserved_instances.rb +51 -44
  21. data/lib/ec2/right_ec2_security_groups.rb +396 -0
  22. data/lib/ec2/right_ec2_spot_instances.rb +425 -0
  23. data/lib/ec2/right_ec2_tags.rb +139 -0
  24. data/lib/ec2/right_ec2_vpc.rb +152 -140
  25. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  26. data/lib/elb/right_elb_interface.rb +205 -39
  27. data/lib/iam/right_iam_access_keys.rb +71 -0
  28. data/lib/iam/right_iam_groups.rb +195 -0
  29. data/lib/iam/right_iam_interface.rb +341 -0
  30. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  31. data/lib/iam/right_iam_users.rb +251 -0
  32. data/lib/rds/right_rds_interface.rb +591 -205
  33. data/lib/right_aws.rb +16 -12
  34. data/lib/route_53/right_route_53_interface.rb +640 -0
  35. data/lib/s3/right_s3.rb +34 -13
  36. data/lib/s3/right_s3_interface.rb +17 -14
  37. data/lib/sdb/active_sdb.rb +215 -38
  38. data/lib/sdb/right_sdb_interface.rb +93 -12
  39. data/lib/sqs/right_sqs.rb +1 -2
  40. data/lib/sqs/right_sqs_gen2.rb +0 -1
  41. data/lib/sqs/right_sqs_gen2_interface.rb +9 -9
  42. data/lib/sqs/right_sqs_interface.rb +6 -7
  43. data/right_aws.gemspec +91 -0
  44. data/test/README.mdown +39 -0
  45. data/test/acf/test_helper.rb +0 -0
  46. data/test/acf/test_right_acf.rb +10 -18
  47. data/test/awsbase/test_helper.rb +0 -0
  48. data/test/awsbase/test_right_awsbase.rb +0 -1
  49. data/test/ec2/test_helper.rb +0 -0
  50. data/test/ec2/test_right_ec2.rb +0 -1
  51. data/test/elb/test_helper.rb +2 -0
  52. data/test/elb/test_right_elb.rb +43 -0
  53. data/test/http_connection.rb +0 -0
  54. data/test/route_53/fixtures/a_record.xml +18 -0
  55. data/test/route_53/fixtures/alias_record.xml +18 -0
  56. data/test/route_53/test_helper.rb +2 -0
  57. data/test/route_53/test_right_route_53.rb +141 -0
  58. data/test/s3/test_helper.rb +0 -0
  59. data/test/s3/test_right_s3.rb +11 -9
  60. data/test/s3/test_right_s3_stubbed.rb +6 -4
  61. data/test/sdb/test_active_sdb.rb +71 -13
  62. data/test/sdb/test_batch_put_attributes.rb +54 -0
  63. data/test/sdb/test_helper.rb +0 -0
  64. data/test/sdb/test_right_sdb.rb +13 -7
  65. data/test/sqs/test_helper.rb +0 -0
  66. data/test/sqs/test_right_sqs.rb +0 -6
  67. data/test/sqs/test_right_sqs_gen2.rb +22 -34
  68. data/test/test_credentials.rb +0 -0
  69. data/test/ts_right_aws.rb +0 -0
  70. metadata +146 -16
  71. data/VERSION +0 -1
@@ -54,7 +54,6 @@ module RightAws
54
54
  # * <tt>:server</tt>: ACW service host, default: DEFAULT_HOST
55
55
  # * <tt>:port</tt>: ACW service port, default: DEFAULT_PORT
56
56
  # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
57
- # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
58
57
  # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
59
58
  # * <tt>:signature_version</tt>: The signature version : '0','1' or '2'(default)
60
59
  # * <tt>:cache</tt>: true/false(default): list_metrics
@@ -144,8 +143,8 @@ module RightAws
144
143
  # Period (60 sec by default)
145
144
  period = (options[:period] && options[:period].to_i) || 60
146
145
  # Statistics ('Average' by default)
147
- statistics = options[:statistics].to_a.flatten
148
- statistics = statistics.blank? ? ['Average'] : statistics.map{|statistic| statistic.to_s.capitalize }
146
+ statistics = Array(options[:statistics]).flatten
147
+ statistics = statistics.right_blank? ? ['Average'] : statistics.map{|statistic| statistic.to_s.capitalize }
149
148
  # Times (5.min.ago up to now by default)
150
149
  start_time = options[:start_time] || (Time.now.utc - 5*60)
151
150
  start_time = start_time.utc.strftime("%Y-%m-%dT%H:%M:%S+00:00") if start_time.is_a?(Time)
@@ -167,7 +166,7 @@ module RightAws
167
166
  # dimentions
168
167
  dim = []
169
168
  dimentions.each do |key, values|
170
- values.to_a.each { |value| dim << [key, value] }
169
+ Array(values).each { |value| dim << [key, value] }
171
170
  end
172
171
  request_hash.merge!(amazonize_list(['Dimensions.member.?.Name', 'Dimensions.member.?.Value'], dim))
173
172
  #
@@ -199,7 +198,7 @@ module RightAws
199
198
  end
200
199
  def tagend(name)
201
200
  case name
202
- when 'Timestamp' then @item[:timestamp] = Time.parse(@text)
201
+ when 'Timestamp' then @item[:timestamp] = @text
203
202
  when 'Unit' then @item[:unit] = @text
204
203
  when 'CustomUnit' then @item[:custom_unit] = @text
205
204
  when 'Samples' then @item[:samples] = @text.to_f
@@ -51,7 +51,7 @@ module RightAws
51
51
  # as.create_or_update_scaling_trigger('kd.tr.1', 'CentOS.5.1-c-array',
52
52
  # :measure_name => 'CPUUtilization',
53
53
  # :statistic => :average,
54
- # :dimentions => {
54
+ # :dimensions => {
55
55
  # 'AutoScalingGroupName' => 'CentOS.5.1-c-array',
56
56
  # 'Namespace' => 'AWS',
57
57
  # 'Service' => 'EC2' },
@@ -95,7 +95,6 @@ module RightAws
95
95
  # * <tt>:server</tt>: AS service host, default: DEFAULT_HOST
96
96
  # * <tt>:port</tt>: AS service port, default: DEFAULT_PORT
97
97
  # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
98
- # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
99
98
  # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
100
99
  # * <tt>:signature_version</tt>: The signature version : '0','1' or '2'(default)
101
100
  # * <tt>:cache</tt>: true/false(default): describe_auto_scaling_groups
@@ -136,7 +135,7 @@ module RightAws
136
135
  auto_scaling_group_names = auto_scaling_group_names.flatten.compact
137
136
  request_hash = amazonize_list('AutoScalingGroupNames.member', auto_scaling_group_names)
138
137
  link = generate_request("DescribeAutoScalingGroups", request_hash)
139
- request_cache_or_info(:describe_auto_scaling_groups, link, DescribeAutoScalingGroupsParser, @@bench, auto_scaling_group_names.blank?)
138
+ request_cache_or_info(:describe_auto_scaling_groups, link, DescribeAutoScalingGroupsParser, @@bench, auto_scaling_group_names.right_blank?)
140
139
  end
141
140
 
142
141
  # Creates a new auto scaling group with the specified name.
@@ -153,8 +152,8 @@ module RightAws
153
152
  options[:min_size] ||= 1
154
153
  options[:max_size] ||= 20
155
154
  options[:cooldown] ||= 0
156
- request_hash = amazonize_list('AvailabilityZones.member', availability_zones.to_a)
157
- request_hash.merge!( amazonize_list('LoadBalancerNames', options[:load_balancer_names].to_a) )
155
+ request_hash = amazonize_list('AvailabilityZones.member', availability_zones)
156
+ request_hash.merge!( amazonize_list('LoadBalancerNames.member', options[:load_balancer_names]) )
158
157
  request_hash.merge!( 'AutoScalingGroupName' => auto_scaling_group_name,
159
158
  'LaunchConfigurationName' => launch_configuration_name,
160
159
  'MinSize' => options[:min_size],
@@ -204,7 +203,7 @@ module RightAws
204
203
  # as.update_auto_scaling_group('CentOS.5.1-c', :min_size => 1, :max_size => 4) #=> true
205
204
  #
206
205
  def update_auto_scaling_group(auto_scaling_group_name, options={})
207
- request_hash = amazonize_list('AvailabilityZones.member', options[:availability_zones].to_a)
206
+ request_hash = amazonize_list('AvailabilityZones.member', options[:availability_zones])
208
207
  request_hash['AutoScalingGroupName'] = auto_scaling_group_name
209
208
  request_hash['LaunchConfigurationName'] = options[:launch_configuration_name] if options[:launch_configuration_name]
210
209
  request_hash['MinSize'] = options[:min_size] if options[:min_size]
@@ -302,7 +301,7 @@ module RightAws
302
301
  link = generate_request("DescribeScalingActivities", request_hash)
303
302
  last_response = request_info( link, DescribeScalingActivitiesParser.new(:logger => @logger))
304
303
  request_hash['NextToken'] = last_response[:next_token]
305
- break unless block && block.call(last_response) && !last_response[:next_token].blank?
304
+ break unless block && block.call(last_response) && !last_response[:next_token].right_blank?
306
305
  end
307
306
  last_response
308
307
  end
@@ -339,21 +338,26 @@ module RightAws
339
338
  # Options: +:security_groups+, +:block_device_mappings+, +:key_name+,
340
339
  # +:user_data+, +:kernel_id+, +:ramdisk_id+
341
340
  #
342
- # as.create_launch_configuration('CentOS.5.1-c', 'ami-08f41161', 'm1.small',
343
- # :key_name => 'kd-moo-test',
344
- # :security_groups => ['default'],
345
- # :user_data => "Woohoo: CentOS.5.1-c" ) #=> true
341
+ # as.create_launch_configuration('kd: CentOS.5.1-c.1', 'ami-08f41161', 'c1.medium',
342
+ # :key_name => 'tim',
343
+ # :security_groups => ['default'],
344
+ # :user_data => "Woohoo: CentOS.5.1-c",
345
+ # :block_device_mappings => [ { :device_name => '/dev/sdk',
346
+ # :ebs_snapshot_id => 'snap-145cbc7d',
347
+ # :ebs_delete_on_termination => true,
348
+ # :ebs_volume_size => 3,
349
+ # :virtual_name => 'ephemeral2'
350
+ # } ]
351
+ # ) #=> true
346
352
  #
347
353
  def create_launch_configuration(launch_configuration_name, image_id, instance_type, options={})
348
- availability_zones = availability_zones.to_a
349
354
  request_hash = { 'LaunchConfigurationName' => launch_configuration_name,
350
355
  'ImageId' => image_id,
351
356
  'InstanceType' => instance_type }
352
- request_hash.merge!(amazonize_list('SecurityGroups.member', options[:security_groups])) unless options[:security_groups].blank?
353
- request_hash.merge!(amazonize_list(['BlockDeviceMappings.member.?.DeviceName', 'BlockDeviceMappings.member.?.VirtualName'],
354
- options[:block_device_mappings].to_a)) unless options[:block_device_mappings].blank?
357
+ request_hash.merge!(amazonize_list('SecurityGroups.member', options[:security_groups])) unless options[:security_groups].right_blank?
358
+ request_hash.merge!(amazonize_block_device_mappings(options[:block_device_mappings], 'BlockDeviceMappings.member'))
355
359
  request_hash['KeyName'] = options[:key_name] if options[:key_name]
356
- request_hash['UserData'] = options[:user_data] if options[:user_data]
360
+ request_hash['UserData'] = Base64.encode64(options[:user_data]).delete("\n") unless options[:user_data].right_blank? if options[:user_data]
357
361
  request_hash['KernelId'] = options[:kernel_id] if options[:kernel_id]
358
362
  request_hash['RamdiskId'] = options[:ramdisk_id] if options[:ramdisk_id]
359
363
  link = generate_request("CreateLaunchConfiguration", request_hash)
@@ -365,16 +369,17 @@ module RightAws
365
369
  # Returns an array of configurations.
366
370
  #
367
371
  # as.describe_launch_configurations #=>
368
- # [{:created_time=>Thu May 28 09:31:20 UTC 2009,
369
- # :kernel_id=>"",
370
- # :launch_configuration_name=>"CentOS.5.1-c",
372
+ # [{:security_groups=>["default"],
371
373
  # :ramdisk_id=>"",
372
- # :security_groups=>["default"],
373
- # :key_name=>"kd-moo-test",
374
- # :user_data=>"Woohoo: CentOS.5.1-c-array",
374
+ # :user_data=>"V29vaG9vOiBDZW50T1MuNS4xLWM=",
375
+ # :instance_type=>"c1.medium",
376
+ # :block_device_mappings=>
377
+ # [{:virtual_name=>"ephemeral2", :device_name=>"/dev/sdk"}],
378
+ # :launch_configuration_name=>"kd: CentOS.5.1-c.1",
379
+ # :created_time=>"2010-03-29T10:00:32.742Z",
375
380
  # :image_id=>"ami-08f41161",
376
- # :block_device_mappings=>[],
377
- # :instance_type=>"m1.small"}, ... ]
381
+ # :key_name=>"tim",
382
+ # :kernel_id=>""}, ...]
378
383
  #
379
384
  def describe_launch_configurations(*launch_configuration_names)
380
385
  result = []
@@ -422,7 +427,7 @@ module RightAws
422
427
  link = generate_request("DescribeLaunchConfigurations", request_hash)
423
428
  last_response = request_info( link, DescribeLaunchConfigurationsParser.new(:logger => @logger) )
424
429
  request_hash['NextToken'] = last_response[:next_token]
425
- break unless block && block.call(last_response) && !last_response[:next_token].blank?
430
+ break unless block && block.call(last_response) && !last_response[:next_token].right_blank?
426
431
  end
427
432
  last_response
428
433
  end
@@ -449,12 +454,12 @@ module RightAws
449
454
  # Returns +true+ or an exception.
450
455
  #
451
456
  # Options: +:measure_name+, +:statistic+, +:period+, +:lower_threshold+, +:lower_breach_scale_increment+,
452
- # +:upper_threshold+, +:upper_breach_scale_increment+, +:dimentions+, +:breach_duration+, +:unit+, +:custom_unit+
457
+ # +:upper_threshold+, +:upper_breach_scale_increment+, +:dimensions+, +:breach_duration+, +:unit+, +:custom_unit+
453
458
  #
454
459
  # as.create_or_update_scaling_trigger('kd.tr.1', 'CentOS.5.1-c-array',
455
460
  # :measure_name => 'CPUUtilization',
456
461
  # :statistic => :average,
457
- # :dimentions => {
462
+ # :dimensions => {
458
463
  # 'AutoScalingGroupName' => 'CentOS.5.1-c-array',
459
464
  # 'Namespace' => 'AWS',
460
465
  # 'Service' => 'EC2' },
@@ -478,11 +483,11 @@ module RightAws
478
483
  'BreachDuration' => options[:breach_duration] }
479
484
  request_hash['Unit'] = options[:unit] if options[:unit]
480
485
  request_hash['CustomUnit'] = options[:custom_unit] if options[:custom_unit]
481
- dimentions = []
482
- (options[:dimentions] || {}).each do |key, values|
483
- values.to_a.each { |value| dimentions << [key, value] }
486
+ dimensions = []
487
+ (options[:dimensions] || {}).each do |key, values|
488
+ Array(values).each { |value| dimensions << [key, value] }
484
489
  end
485
- request_hash.merge!(amazonize_list(['Dimensions.member.?.Name', 'Dimensions.member.?.Value'], dimentions))
490
+ request_hash.merge!(amazonize_list(['Dimensions.member.?.Name', 'Dimensions.member.?.Value'], dimensions))
486
491
  link = generate_request("CreateOrUpdateScalingTrigger", request_hash)
487
492
  request_info(link, RightHttp2xxParser.new(:logger => @logger))
488
493
  end
@@ -537,8 +542,8 @@ module RightAws
537
542
  def tagend(name)
538
543
  case name
539
544
  when 'ActivityId' then @item[:activity_id] = @text
540
- when 'StartTime' then @item[:start_time] = Time::parse(@text)
541
- when 'EndTime' then @item[:end_time] = Time::parse(@text)
545
+ when 'StartTime' then @item[:start_time] = @text
546
+ when 'EndTime' then @item[:end_time] = @text
542
547
  when 'Progress' then @item[:progress] = @text.to_i
543
548
  when 'StatusCode' then @item[:status_code] = @text
544
549
  when 'Cause' then @item[:cause] = @text
@@ -570,7 +575,7 @@ module RightAws
570
575
  end
571
576
  def tagend(name)
572
577
  case name
573
- when 'CreatedTime' then @item[:created_time] = Time::parse(@text)
578
+ when 'CreatedTime' then @item[:created_time] = @text
574
579
  when 'MinSize' then @item[:min_size] = @text.to_i
575
580
  when 'MaxSize' then @item[:max_size] = @text.to_i
576
581
  when 'DesiredCapacity' then @item[:desired_capacity] = @text.to_i
@@ -603,18 +608,17 @@ module RightAws
603
608
 
604
609
  class DescribeLaunchConfigurationsParser < RightAWSParser #:nodoc:
605
610
  def tagstart(name, attributes)
606
- case name
607
- when 'member'
608
- case @xmlpath
609
- when @p
610
- @item = { :block_device_mappings => [],
611
- :security_groups => [] }
612
- end
611
+ case full_tag_name
612
+ when %r{/LaunchConfigurations/member$}
613
+ @item = { :block_device_mappings => [],
614
+ :security_groups => [] }
615
+ when %r{/BlockDeviceMappings/member$}
616
+ @block_device_mapping = {}
613
617
  end
614
618
  end
615
619
  def tagend(name)
616
620
  case name
617
- when 'CreatedTime' then @item[:created_time] = Time::parse(@text)
621
+ when 'CreatedTime' then @item[:created_time] = @text
618
622
  when 'InstanceType' then @item[:instance_type] = @text
619
623
  when 'KeyName' then @item[:key_name] = @text
620
624
  when 'ImageId' then @item[:image_id] = @text
@@ -622,20 +626,24 @@ module RightAws
622
626
  when 'RamdiskId' then @item[:ramdisk_id] = @text
623
627
  when 'LaunchConfigurationName' then @item[:launch_configuration_name] = @text
624
628
  when 'UserData' then @item[:user_data] = @text
625
- when 'member'
626
- case @xmlpath
627
- when "#@p/member/BlockDeviceMappings" then @item[:block_device_mappings] << @text
628
- when "#@p/member/SecurityGroups" then @item[:security_groups] << @text
629
- when @p
630
- @item[:block_device_mappings].sort!
629
+ when 'NextToken' then @result[:next_token] = @text
630
+ else
631
+ case full_tag_name
632
+ when %r{/BlockDeviceMappings/member} # no trailing $
633
+ case name
634
+ when 'DeviceName' then @block_device_mapping[:device_name] = @text
635
+ when 'VirtualName' then @block_device_mapping[:virtual_name] = @text
636
+ when 'member' then @item[:block_device_mappings] << @block_device_mapping
637
+ end
638
+ when %r{member/SecurityGroups/member$}
639
+ @item[:security_groups] << @text
640
+ when %r{/LaunchConfigurations/member$}
631
641
  @item[:security_groups].sort!
632
642
  @result[:launch_configurations] << @item
633
643
  end
634
- when 'NextToken' then @result[:next_token] = @text
635
644
  end
636
645
  end
637
646
  def reset
638
- @p = 'DescribeLaunchConfigurationsResponse/DescribeLaunchConfigurationsResult/LaunchConfigurations'
639
647
  @result = { :launch_configurations => []}
640
648
  end
641
649
  end
@@ -660,7 +668,7 @@ module RightAws
660
668
  case name
661
669
  when 'AutoScalingGroupName' then @item[:auto_scaling_group_name] = @text
662
670
  when 'MeasureName' then @item[:measure_name] = @text
663
- when 'CreatedTime' then @item[:created_time] = Time::parse(@text)
671
+ when 'CreatedTime' then @item[:created_time] = @text
664
672
  when 'BreachDuration' then @item[:breach_duration] = @text.to_i
665
673
  when 'UpperBreachScaleIncrement' then @item[:upper_breach_scale_increment] = @text.to_i
666
674
  when 'UpperThreshold' then @item[:upper_threshold] = @text.to_f
File without changes
@@ -24,7 +24,6 @@
24
24
  # Test
25
25
  module RightAws
26
26
  require 'digest/md5'
27
- require 'pp'
28
27
 
29
28
  class AwsUtils #:nodoc:
30
29
  @@digest1 = OpenSSL::Digest::Digest.new("sha1")
@@ -32,6 +31,13 @@ module RightAws
32
31
  if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
33
32
  @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
34
33
  end
34
+
35
+ def self.utc_iso8601(time)
36
+ if time.is_a?(Fixnum) then time = Time::at(time)
37
+ elsif time.is_a?(String) then time = Time::parse(time)
38
+ end
39
+ time.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
40
+ end
35
41
 
36
42
  def self.sign(aws_secret_access_key, auth_string)
37
43
  Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
@@ -45,9 +51,17 @@ module RightAws
45
51
  end
46
52
  end
47
53
 
54
+ def self.xml_escape(text) # :nodoc:
55
+ REXML::Text::normalize(text)
56
+ end
57
+
58
+ def self.xml_unescape(text) # :nodoc:
59
+ REXML::Text::unnormalize(text)
60
+ end
61
+
48
62
  # Set a timestamp and a signature version
49
63
  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"]
64
+ service_hash["Timestamp"] ||= utc_iso8601(Time.now) unless service_hash["Expires"]
51
65
  service_hash["SignatureVersion"] = signature
52
66
  service_hash
53
67
  end
@@ -124,10 +138,24 @@ module RightAws
124
138
  end
125
139
 
126
140
  def self.split_items_and_params(array)
127
- items = array.to_a.flatten.compact
141
+ items = Array(array).flatten.compact
128
142
  params = items.last.kind_of?(Hash) ? items.pop : {}
129
143
  [items, params]
130
144
  end
145
+
146
+ # Generates a token in format of:
147
+ # 1. "1dd8d4e4-db6b-11df-b31d-0025b37efad0 (if UUID gem is loaded)
148
+ # 2. "1287483761-855215-zSv2z-bWGj2-31M5t-ags9m" (if UUID gem is not loaded)
149
+ TOKEN_GENERATOR_CHARSET = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
150
+ def self.generate_unique_token
151
+ time = Time.now
152
+ token = "%d-%06d" % [time.to_i, time.usec]
153
+ 4.times do
154
+ token << "-"
155
+ 5.times { token << TOKEN_GENERATOR_CHARSET[rand(TOKEN_GENERATOR_CHARSET.size)] }
156
+ end
157
+ token
158
+ end
131
159
  end
132
160
 
133
161
  class AwsBenchmarkingBlock #:nodoc:
@@ -173,7 +201,31 @@ module RightAws
173
201
  def self.amazon_problems=(problems_list)
174
202
  @@amazon_problems = problems_list
175
203
  end
176
-
204
+
205
+ # Raise an exception if a timeout occures while an API call is in progress.
206
+ # This helps to avoid a duplicate resources creation when Amazon hangs for some time and
207
+ # RightHttpConnection is forced to use retries to get a response from it.
208
+ #
209
+ # If an API call action is in the list then no attempts to retry are performed.
210
+ #
211
+ RAISE_ON_TIMEOUT_ON_ACTIONS = %w{
212
+ AllocateAddress
213
+ CreateSnapshot
214
+ CreateVolume
215
+ PurchaseReservedInstancesOffering
216
+ RequestSpotInstances
217
+ RunInstances
218
+ }
219
+ @@raise_on_timeout_on_actions = RAISE_ON_TIMEOUT_ON_ACTIONS.dup
220
+
221
+ def self.raise_on_timeout_on_actions
222
+ @@raise_on_timeout_on_actions
223
+ end
224
+
225
+ def self.raise_on_timeout_on_actions=(actions_list)
226
+ @@raise_on_timeout_on_actions = actions_list
227
+ end
228
+
177
229
  end
178
230
 
179
231
  module RightAwsBaseInterface
@@ -214,33 +266,42 @@ module RightAws
214
266
  @params = params
215
267
  # If one defines EC2_URL he may forget to use a single slash as an "empty service" path.
216
268
  # Amazon does not like this therefore add this bad boy if he is missing...
217
- service_info[:default_service] = '/' if service_info[:default_service].blank?
269
+ service_info[:default_service] = '/' if service_info[:default_service].right_blank?
218
270
  raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
219
- if aws_access_key_id.blank? || aws_secret_access_key.blank?
271
+ if aws_access_key_id.right_blank? || aws_secret_access_key.right_blank?
220
272
  @aws_access_key_id = aws_access_key_id
221
273
  @aws_secret_access_key = aws_secret_access_key
222
274
  # if the endpoint was explicitly defined - then use it
223
275
  if @params[:endpoint_url]
224
- @params[:server] = URI.parse(@params[:endpoint_url]).host
225
- @params[:port] = URI.parse(@params[:endpoint_url]).port
226
- @params[:service] = URI.parse(@params[:endpoint_url]).path
276
+ uri = URI.parse(@params[:endpoint_url])
277
+ @params[:server] = uri.host
278
+ @params[:port] = uri.port
279
+ @params[:service] = uri.path
280
+ @params[:protocol] = uri.scheme
227
281
  # make sure the 'service' path is not empty
228
- @params[:service] = service_info[:default_service] if @params[:service].blank?
229
- @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
282
+ @params[:service] = service_info[:default_service] if @params[:service].right_blank?
230
283
  @params[:region] = nil
284
+ default_port = uri.default_port
231
285
  else
232
286
  @params[:server] ||= service_info[:default_host]
233
287
  @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
234
288
  @params[:port] ||= service_info[:default_port]
235
289
  @params[:service] ||= service_info[:default_service]
236
290
  @params[:protocol] ||= service_info[:default_protocol]
291
+ default_port = @params[:protocol] == 'https' ? 443 : 80
237
292
  end
238
- # @params[:multi_thread] ||= defined?(AWS_DAEMON)
293
+ # build a host name to sign
294
+ @params[:host_to_sign] = @params[:server].dup
295
+ @params[:host_to_sign] << ":#{@params[:port]}" unless default_port == @params[:port].to_i
296
+ # a set of options to be passed to RightHttpConnection object
297
+ @params[:connection_options] = {} unless @params[:connection_options].is_a?(Hash)
298
+ @with_connection_options = {}
239
299
  @params[:connections] ||= :shared # || :dedicated
240
300
  @params[:max_connections] ||= 10
241
301
  @params[:connection_lifetime] ||= 20*60
242
302
  @params[:api_version] ||= service_info[:default_api_version]
243
303
  @logger = @params[:logger]
304
+ @logger = ::Rails.logger if !@logger && defined?(::Rails) && ::Rails.respond_to?(:logger)
244
305
  @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
245
306
  @logger = Logger.new(STDOUT) if !@logger
246
307
  @logger.info "New #{self.class.name} using #{@params[:connections]} connections mode"
@@ -275,7 +336,8 @@ module RightAws
275
336
  # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
276
337
  # feb 04, 2009 (load balancer uses 'RequestId' hence use 'i' modifier to hit it also)
277
338
  response = response.sub(%r{<requestId>.+?</requestId>}i, '')
278
- response_md5 = MD5.md5(response).to_s
339
+ # this should work for both ruby 1.8.x and 1.9.x
340
+ response_md5 = Digest::MD5::new.update(response).to_s
279
341
  # check for changes
280
342
  unless @cache[function] && @cache[function][:response_md5] == response_md5
281
343
  # well, the response is new, reset cache data
@@ -306,11 +368,57 @@ module RightAws
306
368
  raise if $!.is_a?(AwsNoChange)
307
369
  AwsError::on_aws_exception(self, options)
308
370
  end
309
-
310
- # # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
311
- # def multi_thread
312
- # @params[:multi_thread]
313
- # end
371
+
372
+ #----------------------------
373
+ # HTTP Connections handling
374
+ #----------------------------
375
+
376
+ def get_server_url(request) # :nodoc:
377
+ "#{request[:protocol]}://#{request[:server]}:#{request[:port]}"
378
+ end
379
+
380
+ def get_connections_storage(aws_service) # :nodoc:
381
+ case @params[:connections].to_s
382
+ when 'dedicated' then @connections_storage ||= {}
383
+ else Thread.current[aws_service] ||= {}
384
+ end
385
+ end
386
+
387
+ def destroy_connection(request, reason) # :nodoc:
388
+ connections = get_connections_storage(request[:aws_service])
389
+ server_url = get_server_url(request)
390
+ if connections[server_url]
391
+ connections[server_url][:connection].finish(reason)
392
+ connections.delete(server_url)
393
+ end
394
+ end
395
+
396
+ # Expire the connection if it has expired.
397
+ def get_connection(request) # :nodoc:
398
+ server_url = get_server_url(request)
399
+ connection_storage = get_connections_storage(request[:aws_service])
400
+ life_time_scratch = Time.now-@params[:connection_lifetime]
401
+ # Delete out-of-dated connections
402
+ connections_in_list = 0
403
+ connection_storage.to_a.sort{|conn1, conn2| conn2[1][:last_used_at] <=> conn1[1][:last_used_at]}.each do |serv_url, conn_opts|
404
+ if @params[:max_connections] <= connections_in_list
405
+ conn_opts[:connection].finish('out-of-limit')
406
+ connection_storage.delete(server_url)
407
+ elsif conn_opts[:last_used_at] < life_time_scratch
408
+ conn_opts[:connection].finish('out-of-date')
409
+ connection_storage.delete(server_url)
410
+ else
411
+ connections_in_list += 1
412
+ end
413
+ end
414
+ connection = (connection_storage[server_url] ||= {})
415
+ connection[:last_used_at] = Time.now
416
+ connection[:connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
417
+ end
418
+
419
+ #----------------------------
420
+ # HTTP Requests handling
421
+ #----------------------------
314
422
 
315
423
  # ACF, AMS, EC2, LBS and SDB uses this guy
316
424
  # SQS and S3 use their own methods
@@ -325,13 +433,13 @@ module RightAws
325
433
  "Version" => @params[:api_version] }
326
434
  service_hash.merge!(options)
327
435
  # Sign request options
328
- service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:server], @params[:service])
436
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:host_to_sign], @params[:service])
329
437
  # Use POST if the length of the query string is too large
330
438
  # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/MakingRESTRequests.html
331
439
  if http_verb != 'POST' && service_params.size > 2000
332
440
  http_verb = 'POST'
333
441
  if signature_version == '2'
334
- service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:server], @params[:service])
442
+ service_params = signed_service_params(@aws_secret_access_key, service_hash, http_verb, @params[:host_to_sign], @params[:service])
335
443
  end
336
444
  end
337
445
  # create a request
@@ -341,48 +449,31 @@ module RightAws
341
449
  when 'POST'
342
450
  request = Net::HTTP::Post.new(@params[:service])
343
451
  request.body = service_params
344
- request['Content-Type'] = 'application/x-www-form-urlencoded'
452
+ request['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
345
453
  else
346
454
  raise "Unsupported HTTP verb #{verb.inspect}!"
347
455
  end
348
456
  # prepare output hash
349
- { :request => request,
350
- :server => @params[:server],
351
- :port => @params[:port],
352
- :protocol => @params[:protocol] }
353
- end
354
-
355
- def get_connection(aws_service, request) #:nodoc
356
- server_url = "#{request[:protocol]}://#{request[:server]}:#{request[:port]}}"
357
- #
358
- case @params[:connections].to_s
359
- when 'dedicated'
360
- @connections_storage ||= {}
361
- else # 'dedicated'
362
- @connections_storage = (Thread.current[aws_service] ||= {})
363
- end
364
- #
365
- @connections_storage[server_url] ||= {}
366
- @connections_storage[server_url][:last_used_at] = Time.now
367
- @connections_storage[server_url][:connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
368
- # keep X most recent connections (but were used not far than Y minutes ago)
369
- connections = 0
370
- @connections_storage.to_a.sort{|i1, i2| i2[1][:last_used_at] <=> i1[1][:last_used_at]}.to_a.each do |i|
371
- if i[0] != server_url && (@params[:max_connections] <= connections || i[1][:last_used_at] < Time.now - @params[:connection_lifetime])
372
- # delete the connection from the list
373
- @connections_storage.delete(i[0])
374
- # then finish it
375
- i[1][:connection].finish((@params[:max_connections] <= connections) ? "out-of-limit" : "out-of-date") rescue nil
376
- else
377
- connections += 1
378
- end
457
+ request_hash = { :request => request,
458
+ :server => @params[:server],
459
+ :port => @params[:port],
460
+ :protocol => @params[:protocol] }
461
+ request_hash.merge!(@params[:connection_options])
462
+ request_hash.merge!(@with_connection_options)
463
+
464
+ # If an action is marked as "non-retryable" and there was no :raise_on_timeout option set
465
+ # explicitly then do set that option
466
+ if Array(RightAwsBase::raise_on_timeout_on_actions).include?(action) && !request_hash.has_key?(:raise_on_timeout)
467
+ request_hash.merge!(:raise_on_timeout => true)
379
468
  end
380
- @connections_storage[server_url][:connection]
469
+
470
+ request_hash
381
471
  end
382
472
 
383
473
  # All services uses this guy.
384
474
  def request_info_impl(aws_service, benchblock, request, parser, &block) #:nodoc:
385
- @connection = get_connection(aws_service, request)
475
+ request[:aws_service] = aws_service
476
+ @connection = get_connection(request)
386
477
  @last_request = request[:request]
387
478
  @last_response = nil
388
479
  response = nil
@@ -397,25 +488,31 @@ module RightAws
397
488
  # Exceptions can originate from code directly in the block, or from user
398
489
  # code called in the other block which is passed to response.read_body.
399
490
  benchblock.service.add! do
400
- responsehdr = @connection.request(request) do |response|
401
- #########
402
- begin
403
- @last_response = response
404
- if response.is_a?(Net::HTTPSuccess)
405
- @error_handler = nil
406
- response.read_body(&block)
407
- else
408
- @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
409
- check_result = @error_handler.check(request)
410
- if check_result
491
+ begin
492
+ responsehdr = @connection.request(request) do |response|
493
+ #########
494
+ begin
495
+ @last_response = response
496
+ if response.is_a?(Net::HTTPSuccess)
411
497
  @error_handler = nil
412
- return check_result
498
+ response.read_body(&block)
499
+ else
500
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
501
+ check_result = @error_handler.check(request)
502
+ if check_result
503
+ @error_handler = nil
504
+ return check_result
505
+ end
506
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
413
507
  end
414
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
508
+ rescue Exception => e
509
+ blockexception = e
415
510
  end
416
- rescue Exception => e
417
- blockexception = e
418
511
  end
512
+ rescue Exception => e
513
+ # Kill a connection if we run into a low level connection error
514
+ destroy_connection(request, "error: #{e.message}")
515
+ raise e
419
516
  end
420
517
  #########
421
518
 
@@ -429,7 +526,15 @@ module RightAws
429
526
  return parser.result
430
527
  end
431
528
  else
432
- benchblock.service.add!{ response = @connection.request(request) }
529
+ benchblock.service.add! do
530
+ begin
531
+ response = @connection.request(request)
532
+ rescue Exception => e
533
+ # Kill a connection if we run into a low level connection error
534
+ destroy_connection(request, "error: #{e.message}")
535
+ raise e
536
+ end
537
+ end
433
538
  # check response for errors...
434
539
  @last_response = response
435
540
  if response.is_a?(Net::HTTPSuccess)
@@ -451,7 +556,7 @@ module RightAws
451
556
  raise
452
557
  end
453
558
 
454
- def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
559
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true, &block) #:nodoc:
455
560
  # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
456
561
  # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
457
562
  # If the caching is enabled and hit then throw AwsNoChange.
@@ -461,7 +566,7 @@ module RightAws
461
566
  cache_hits?(method.to_sym, response.body) if use_cache
462
567
  parser = parser_class.new(:logger => @logger)
463
568
  benchblock.xml.add!{ parser.parse(response, params) }
464
- result = block_given? ? yield(parser) : parser.result
569
+ result = block ? block.call(parser) : parser.result
465
570
  # update parsed data
466
571
  update_cache(method.to_sym, :parsed => result) if use_cache
467
572
  result
@@ -472,7 +577,24 @@ module RightAws
472
577
  @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}i] && $1
473
578
  end
474
579
 
580
+ # Incrementally lists something.
581
+ def incrementally_list_items(action, parser_class, params={}, &block) # :nodoc:
582
+ params = params.dup
583
+ params['MaxItems'] = params.delete(:max_items) if params[:max_items]
584
+ params['Marker'] = params.delete(:marker) if params[:marker]
585
+ last_response = nil
586
+ loop do
587
+ last_response = request_info( generate_request(action, params), parser_class.new(:logger => @logger))
588
+ params['Marker'] = last_response[:marker]
589
+ break unless block && block.call(last_response) && !last_response[:marker].right_blank?
590
+ end
591
+ last_response
592
+ end
593
+
475
594
  # Format array of items into Amazons handy hash ('?' is a place holder):
595
+ # Options:
596
+ # :default => "something" : Set a value to "something" when it is nil
597
+ # :default => :skip_nils : Skip nil values
476
598
  #
477
599
  # amazonize_list('Item', ['a', 'b', 'c']) =>
478
600
  # { 'Item.1' => 'a', 'Item.2' => 'b', 'Item.3' => 'c' }
@@ -496,22 +618,114 @@ module RightAws
496
618
  # "Filter.2.Key"=>"B",
497
619
  # "Filter.2.Value.1"=>"ba",
498
620
  # "Filter.2.Value.2"=>"bb"}
499
- def amazonize_list(masks, list) #:nodoc:
621
+ def amazonize_list(masks, list, options={}) #:nodoc:
500
622
  groups = {}
501
- list.to_a.each_with_index do |list_item, i|
502
- masks.to_a.each_with_index do |mask, mask_idx|
623
+ Array(list).each_with_index do |list_item, i|
624
+ Array(masks).each_with_index do |mask, mask_idx|
503
625
  key = mask[/\?/] ? mask.dup : mask.dup + '.?'
504
626
  key.sub!('?', (i+1).to_s)
505
- value = list_item.to_a[mask_idx]
627
+ value = Array(list_item)[mask_idx]
506
628
  if value.is_a?(Array)
507
- groups.merge!(amazonize_list(key, value))
629
+ groups.merge!(amazonize_list(key, value, options))
508
630
  else
631
+ if value.nil?
632
+ next if options[:default] == :skip_nils
633
+ value = options[:default]
634
+ end
635
+ # Hack to avoid having unhandled '?' in keys : do replace them all with '1':
636
+ # bad: ec2.amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], { a: => :b }) => {"Filter.1.Key"=>:a, "Filter.1.Value.?"=>1}
637
+ # good: ec2.amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], { a: => :b }) => {"Filter.1.Key"=>:a, "Filter.1.Value.1"=>1}
638
+ key.gsub!('?', '1')
509
639
  groups[key] = value
510
640
  end
511
641
  end
512
642
  end
513
643
  groups
514
644
  end
645
+
646
+ BLOCK_DEVICE_KEY_MAPPING = { # :nodoc:
647
+ :device_name => 'DeviceName',
648
+ :virtual_name => 'VirtualName',
649
+ :no_device => 'NoDevice',
650
+ :ebs_snapshot_id => 'Ebs.SnapshotId',
651
+ :ebs_volume_size => 'Ebs.VolumeSize',
652
+ :ebs_delete_on_termination => 'Ebs.DeleteOnTermination' }
653
+
654
+ def amazonize_block_device_mappings(block_device_mappings, key = 'BlockDeviceMapping') # :nodoc:
655
+ result = {}
656
+ unless block_device_mappings.right_blank?
657
+ block_device_mappings = [block_device_mappings] unless block_device_mappings.is_a?(Array)
658
+ block_device_mappings.each_with_index do |b, idx|
659
+ BLOCK_DEVICE_KEY_MAPPING.each do |local_name, remote_name|
660
+ value = b[local_name]
661
+ case local_name
662
+ when :no_device then value = value ? '' : nil # allow to pass :no_device as boolean
663
+ end
664
+ result["#{key}.#{idx+1}.#{remote_name}"] = value unless value.nil?
665
+ end
666
+ end
667
+ end
668
+ result
669
+ end
670
+
671
+ # Execute a block of code with custom set of settings for right_http_connection.
672
+ # Accepts next options (see Rightscale::HttpConnection for explanation):
673
+ # :raise_on_timeout
674
+ # :http_connection_retry_count
675
+ # :http_connection_open_timeout
676
+ # :http_connection_read_timeout
677
+ # :http_connection_retry_delay
678
+ # :user_agent
679
+ # :exception
680
+ #
681
+ # Example #1:
682
+ #
683
+ # # Try to create a snapshot but stop with exception if timeout is received
684
+ # # to avoid having a duplicate API calls that create duplicate snapshots.
685
+ # ec2 = Rightscale::Ec2::new(aws_access_key_id, aws_secret_access_key)
686
+ # ec2.with_connection_options(:raise_on_timeout => true) do
687
+ # ec2.create_snapshot('vol-898a6fe0', 'KD: WooHoo!!')
688
+ # end
689
+ #
690
+ # Example #2:
691
+ #
692
+ # # Opposite case when the setting is global:
693
+ # @ec2 = Rightscale::Ec2::new(aws_access_key_id, aws_secret_access_key,
694
+ # :connection_options => { :raise_on_timeout => true })
695
+ # # Create an SSHKey but do tries on timeout
696
+ # ec2.with_connection_options(:raise_on_timeout => false) do
697
+ # new_key = ec2.create_key_pair('my_test_key')
698
+ # end
699
+ #
700
+ # Example #3:
701
+ #
702
+ # # Global settings (HttpConnection level):
703
+ # Rightscale::HttpConnection::params[:http_connection_open_timeout] = 5
704
+ # Rightscale::HttpConnection::params[:http_connection_read_timeout] = 250
705
+ # Rightscale::HttpConnection::params[:http_connection_retry_count] = 2
706
+ #
707
+ # # Local setings (RightAws level)
708
+ # ec2 = Rightscale::Ec2::new(AWS_ID, AWS_KEY,
709
+ # :region => 'us-east-1',
710
+ # :connection_options => {
711
+ # :http_connection_read_timeout => 2,
712
+ # :http_connection_retry_count => 5,
713
+ # :user_agent => 'Mozilla 4.0'
714
+ # })
715
+ #
716
+ # # Custom settings (API call level)
717
+ # ec2.with_connection_options(:raise_on_timeout => true,
718
+ # :http_connection_read_timeout => 10,
719
+ # :user_agent => '') do
720
+ # pp ec2.describe_images
721
+ # end
722
+ #
723
+ def with_connection_options(options, &block)
724
+ @with_connection_options = options
725
+ block.call self
726
+ ensure
727
+ @with_connection_options = {}
728
+ end
515
729
  end
516
730
 
517
731
 
@@ -632,7 +846,7 @@ module RightAws
632
846
  @reiteration_delay = @@reiteration_start_delay
633
847
  @retries = 0
634
848
  # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
635
- @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
849
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
636
850
  @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
637
851
  end
638
852
 
@@ -675,10 +889,17 @@ module RightAws
675
889
  # Ok, it is a redirect, find the new destination location
676
890
  if redirect_detected
677
891
  location = response['location']
892
+ # As for 301 ( Moved Permanently) Amazon does not return a 'Location' header but
893
+ # it is possible to extract a new endpoint from the response body
894
+ if location.right_blank? && response.code=='301' && response.body
895
+ new_endpoint = response.body[/<Endpoint>(.*?)<\/Endpoint>/] && $1
896
+ location = "#{request[:protocol]}://#{new_endpoint}:#{request[:port]}#{request[:request].path}"
897
+ end
678
898
  # ... log information and ...
679
899
  @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
680
900
  @aws.logger.info(" Old location: #{request_text_data}")
681
901
  @aws.logger.info(" New location: #{location}")
902
+ @aws.logger.info(" Request Verb: #{request[:request].class.name}")
682
903
  # ... fix the connection data
683
904
  request[:server] = URI.parse(location).host
684
905
  request[:protocol] = URI.parse(location).scheme
@@ -701,7 +922,7 @@ module RightAws
701
922
  # It may have a chance that one server is a semi-down and reconnection
702
923
  # will help us to connect to the other server
703
924
  if !redirect_detected && @close_on_error
704
- @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
925
+ @aws.destroy_connection(request, "#{self.class.name}: error match to pattern '#{error_match}'")
705
926
  end
706
927
 
707
928
  if (Time.now < @stop_at)
@@ -730,13 +951,13 @@ module RightAws
730
951
  end
731
952
  # aha, this is unhandled error:
732
953
  elsif @close_on_error
733
- # Is this a 5xx error ?
734
- if @aws.last_response.code.to_s[/^5\d\d$/]
735
- @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
954
+ # On 5xx(Server errors), 403(RequestTimeTooSkewed) and 408(Request Timeout) a conection has to be closed
955
+ if @aws.last_response.code.to_s[/^(5\d\d|403|408)$/]
956
+ @aws.destroy_connection(request, "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'")
736
957
  # Is this a 4xx error ?
737
958
  elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
738
- @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
739
- "probability: #{@close_on_4xx_probability}%"
959
+ @aws.destroy_connection(request, "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
960
+ "probability: #{@close_on_4xx_probability}%")
740
961
  end
741
962
  end
742
963
  result
@@ -747,29 +968,41 @@ module RightAws
747
968
 
748
969
  #-----------------------------------------------------------------
749
970
 
750
- class RightSaxParserCallback #:nodoc:
751
- def self.include_callback
752
- include XML::SaxParser::Callbacks
753
- end
971
+ class RightSaxParserCallbackTemplate #:nodoc:
754
972
  def initialize(right_aws_parser)
755
973
  @right_aws_parser = right_aws_parser
756
974
  end
757
- def on_start_element(name, attr_hash)
758
- @right_aws_parser.tag_start(name, attr_hash)
759
- end
760
975
  def on_characters(chars)
761
976
  @right_aws_parser.text(chars)
762
977
  end
763
- def on_end_element(name)
764
- @right_aws_parser.tag_end(name)
765
- end
766
978
  def on_start_document; end
767
979
  def on_comment(msg); end
768
980
  def on_processing_instruction(target, data); end
769
981
  def on_cdata_block(cdata); end
770
982
  def on_end_document; end
771
983
  end
772
-
984
+
985
+ class RightSaxParserCallback < RightSaxParserCallbackTemplate
986
+ def self.include_callback
987
+ include XML::SaxParser::Callbacks
988
+ end
989
+ def on_start_element(name, attr_hash)
990
+ @right_aws_parser.tag_start(name, attr_hash)
991
+ end
992
+ def on_end_element(name)
993
+ @right_aws_parser.tag_end(name)
994
+ end
995
+ end
996
+
997
+ class RightSaxParserCallbackNs < RightSaxParserCallbackTemplate
998
+ def on_start_element_ns(name, attr_hash, prefix, uri, namespaces)
999
+ @right_aws_parser.tag_start(name, attr_hash)
1000
+ end
1001
+ def on_end_element_ns(name, prefix, uri)
1002
+ @right_aws_parser.tag_end(name)
1003
+ end
1004
+ end
1005
+
773
1006
  class RightAWSParser #:nodoc:
774
1007
  # default parsing library
775
1008
  DEFAULT_XML_LIBRARY = 'rexml'
@@ -830,27 +1063,35 @@ module RightAws
830
1063
  if @xml_lib=='libxml' && !defined?(XML::SaxParser)
831
1064
  begin
832
1065
  require 'xml/libxml'
833
- # is it new ? - Setup SaxParserCallback
834
- if XML::Parser::VERSION >= '0.5.1.0'
835
- RightSaxParserCallback.include_callback
1066
+ # Setup SaxParserCallback
1067
+ if XML::Parser::VERSION >= '0.5.1' &&
1068
+ XML::Parser::VERSION < '0.9.7'
1069
+ RightSaxParserCallback.include_callback
836
1070
  end
837
1071
  rescue LoadError => e
838
- @@supported_xml_libs.delete(@xml_lib)
839
- @xml_lib = DEFAULT_XML_LIBRARY
1072
+ @@supported_xml_libs.delete(@xml_lib)
1073
+ @xml_lib = DEFAULT_XML_LIBRARY
840
1074
  if @logger
841
1075
  @logger.error e.inspect
842
1076
  @logger.error e.backtrace
843
- @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
1077
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
844
1078
  end
845
1079
  end
846
1080
  end
847
1081
  # Parse the xml text
848
1082
  case @xml_lib
849
- when 'libxml'
850
- xml = XML::SaxParser.new
851
- xml.string = xml_text
1083
+ when 'libxml'
1084
+ if XML::Parser::VERSION >= '0.9.9'
1085
+ # avoid warning on every usage
1086
+ xml = XML::SaxParser.string(xml_text)
1087
+ else
1088
+ xml = XML::SaxParser.new
1089
+ xml.string = xml_text
1090
+ end
852
1091
  # check libxml-ruby version
853
- if XML::Parser::VERSION >= '0.5.1.0'
1092
+ if XML::Parser::VERSION >= '0.9.7'
1093
+ xml.callbacks = RightSaxParserCallbackNs.new(self)
1094
+ elsif XML::Parser::VERSION >= '0.5.1'
854
1095
  xml.callbacks = RightSaxParserCallback.new(self)
855
1096
  else
856
1097
  xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}
@@ -929,5 +1170,11 @@ module RightAws
929
1170
  end
930
1171
  end
931
1172
 
1173
+ class RightBoolResponseParser < RightAWSParser #:nodoc:
1174
+ def tagend(name)
1175
+ @result = (@text=='true') if name == 'return'
1176
+ end
1177
+ end
1178
+
932
1179
  end
933
1180