icehouse-right_aws 1.11.0 → 2.2.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 (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