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.
- data/History.txt +93 -15
- data/Manifest.txt +15 -1
- data/README.txt +0 -4
- data/Rakefile +34 -17
- data/lib/acf/right_acf_interface.rb +260 -124
- data/lib/acf/right_acf_invalidations.rb +144 -0
- data/lib/acf/right_acf_origin_access_identities.rb +230 -0
- data/lib/acf/right_acf_streaming_interface.rb +229 -0
- data/lib/acw/right_acw_interface.rb +4 -5
- data/lib/as/right_as_interface.rb +59 -51
- data/lib/awsbase/benchmark_fix.rb +0 -0
- data/lib/awsbase/right_awsbase.rb +351 -104
- data/lib/awsbase/support.rb +2 -82
- data/lib/awsbase/version.rb +9 -0
- data/lib/ec2/right_ec2.rb +97 -246
- data/lib/ec2/right_ec2_ebs.rb +88 -68
- data/lib/ec2/right_ec2_images.rb +90 -50
- data/lib/ec2/right_ec2_instances.rb +118 -89
- data/lib/ec2/right_ec2_placement_groups.rb +108 -0
- data/lib/ec2/right_ec2_reserved_instances.rb +51 -44
- data/lib/ec2/right_ec2_security_groups.rb +396 -0
- data/lib/ec2/right_ec2_spot_instances.rb +425 -0
- data/lib/ec2/right_ec2_tags.rb +139 -0
- data/lib/ec2/right_ec2_vpc.rb +152 -140
- data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
- data/lib/elb/right_elb_interface.rb +205 -39
- data/lib/iam/right_iam_access_keys.rb +71 -0
- data/lib/iam/right_iam_groups.rb +195 -0
- data/lib/iam/right_iam_interface.rb +341 -0
- data/lib/iam/right_iam_mfa_devices.rb +67 -0
- data/lib/iam/right_iam_users.rb +251 -0
- data/lib/rds/right_rds_interface.rb +591 -205
- data/lib/right_aws.rb +16 -12
- data/lib/route_53/right_route_53_interface.rb +640 -0
- data/lib/s3/right_s3.rb +34 -13
- data/lib/s3/right_s3_interface.rb +17 -14
- data/lib/sdb/active_sdb.rb +215 -38
- data/lib/sdb/right_sdb_interface.rb +93 -12
- data/lib/sqs/right_sqs.rb +1 -2
- data/lib/sqs/right_sqs_gen2.rb +0 -1
- data/lib/sqs/right_sqs_gen2_interface.rb +9 -9
- data/lib/sqs/right_sqs_interface.rb +6 -7
- data/right_aws.gemspec +91 -0
- data/test/README.mdown +39 -0
- data/test/acf/test_helper.rb +0 -0
- data/test/acf/test_right_acf.rb +10 -18
- data/test/awsbase/test_helper.rb +0 -0
- data/test/awsbase/test_right_awsbase.rb +0 -1
- data/test/ec2/test_helper.rb +0 -0
- data/test/ec2/test_right_ec2.rb +0 -1
- data/test/elb/test_helper.rb +2 -0
- data/test/elb/test_right_elb.rb +43 -0
- data/test/http_connection.rb +0 -0
- data/test/route_53/fixtures/a_record.xml +18 -0
- data/test/route_53/fixtures/alias_record.xml +18 -0
- data/test/route_53/test_helper.rb +2 -0
- data/test/route_53/test_right_route_53.rb +141 -0
- data/test/s3/test_helper.rb +0 -0
- data/test/s3/test_right_s3.rb +11 -9
- data/test/s3/test_right_s3_stubbed.rb +6 -4
- data/test/sdb/test_active_sdb.rb +71 -13
- data/test/sdb/test_batch_put_attributes.rb +54 -0
- data/test/sdb/test_helper.rb +0 -0
- data/test/sdb/test_right_sdb.rb +13 -7
- data/test/sqs/test_helper.rb +0 -0
- data/test/sqs/test_right_sqs.rb +0 -6
- data/test/sqs/test_right_sqs_gen2.rb +22 -34
- data/test/test_credentials.rb +0 -0
- data/test/ts_right_aws.rb +0 -0
- metadata +146 -16
- 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].
|
148
|
-
statistics = statistics.
|
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.
|
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] =
|
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
|
-
# :
|
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.
|
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
|
157
|
-
request_hash.merge!( amazonize_list('LoadBalancerNames', options[:load_balancer_names]
|
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]
|
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].
|
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', '
|
343
|
-
#
|
344
|
-
#
|
345
|
-
#
|
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].
|
353
|
-
request_hash.merge!(
|
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]
|
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
|
-
# [{:
|
369
|
-
# :kernel_id=>"",
|
370
|
-
# :launch_configuration_name=>"CentOS.5.1-c",
|
372
|
+
# [{:security_groups=>["default"],
|
371
373
|
# :ramdisk_id=>"",
|
372
|
-
# :
|
373
|
-
# :
|
374
|
-
# :
|
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
|
-
# :
|
377
|
-
# :
|
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].
|
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+, +:
|
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
|
-
# :
|
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
|
-
|
482
|
-
(options[:
|
483
|
-
values.
|
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'],
|
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] =
|
541
|
-
when 'EndTime' then @item[:end_time] =
|
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] =
|
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
|
607
|
-
when
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
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] =
|
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 '
|
626
|
-
|
627
|
-
|
628
|
-
when
|
629
|
-
|
630
|
-
@
|
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] =
|
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
|
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.
|
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].
|
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.
|
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
|
-
|
225
|
-
@params[:
|
226
|
-
@params[:
|
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].
|
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
|
-
#
|
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
|
-
|
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
|
-
|
311
|
-
#
|
312
|
-
|
313
|
-
|
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[:
|
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[:
|
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
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
#
|
358
|
-
|
359
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
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
|
-
|
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
|
-
|
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!
|
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 =
|
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.
|
502
|
-
masks.
|
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
|
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.
|
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
|
-
#
|
734
|
-
if @aws.last_response.code.to_s[/^5\d\d$/]
|
735
|
-
@aws.
|
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.
|
739
|
-
|
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
|
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
|
-
#
|
834
|
-
if XML::Parser::VERSION >= '0.5.1
|
835
|
-
|
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
|
-
|
851
|
-
|
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
|
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
|
|