right_aws 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/History.txt +22 -1
  2. data/Manifest.txt +11 -1
  3. data/README.txt +0 -4
  4. data/Rakefile +19 -25
  5. data/lib/acf/right_acf_interface.rb +199 -135
  6. data/lib/acf/right_acf_invalidations.rb +144 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +4 -4
  8. data/lib/acf/right_acf_streaming_interface.rb +19 -26
  9. data/lib/acw/right_acw_interface.rb +1 -2
  10. data/lib/as/right_as_interface.rb +6 -7
  11. data/lib/awsbase/right_awsbase.rb +287 -91
  12. data/lib/awsbase/support.rb +2 -82
  13. data/lib/awsbase/version.rb +9 -0
  14. data/lib/ec2/right_ec2.rb +101 -38
  15. data/lib/ec2/right_ec2_ebs.rb +71 -58
  16. data/lib/ec2/right_ec2_images.rb +82 -42
  17. data/lib/ec2/right_ec2_instances.rb +74 -44
  18. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  19. data/lib/ec2/right_ec2_reserved_instances.rb +50 -46
  20. data/lib/ec2/right_ec2_security_groups.rb +148 -32
  21. data/lib/ec2/right_ec2_spot_instances.rb +53 -27
  22. data/lib/ec2/right_ec2_tags.rb +139 -0
  23. data/lib/ec2/right_ec2_vpc.rb +151 -139
  24. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  25. data/lib/elb/right_elb_interface.rb +93 -18
  26. data/lib/iam/right_iam_access_keys.rb +71 -0
  27. data/lib/iam/right_iam_groups.rb +195 -0
  28. data/lib/iam/right_iam_interface.rb +341 -0
  29. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  30. data/lib/iam/right_iam_users.rb +251 -0
  31. data/lib/rds/right_rds_interface.rb +513 -202
  32. data/lib/right_aws.rb +12 -12
  33. data/lib/route_53/right_route_53_interface.rb +630 -0
  34. data/lib/s3/right_s3.rb +9 -12
  35. data/lib/s3/right_s3_interface.rb +10 -11
  36. data/lib/sdb/active_sdb.rb +18 -33
  37. data/lib/sdb/right_sdb_interface.rb +36 -4
  38. data/lib/sqs/right_sqs.rb +1 -2
  39. data/lib/sqs/right_sqs_gen2.rb +0 -1
  40. data/lib/sqs/right_sqs_gen2_interface.rb +4 -5
  41. data/lib/sqs/right_sqs_interface.rb +6 -7
  42. data/right_aws.gemspec +91 -0
  43. data/test/awsbase/test_helper.rb +2 -0
  44. data/test/awsbase/test_right_awsbase.rb +12 -0
  45. data/test/s3/test_right_s3.rb +1 -1
  46. data/test/sdb/test_active_sdb.rb +1 -1
  47. data/test/sdb/test_batch_put_attributes.rb +54 -0
  48. data/test/sqs/test_right_sqs.rb +0 -6
  49. data/test/sqs/test_right_sqs_gen2.rb +1 -1
  50. metadata +109 -58
@@ -0,0 +1,144 @@
1
+ #
2
+ # Copyright (c) 2010 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ module RightAws
24
+
25
+ class AcfInterface
26
+
27
+ # List Invalidations
28
+ #
29
+ # acf.list_invalidations('E3LTBMK4EAQS7D') #=>
30
+ # [{:status=>"InProgress", :aws_id=>"I3AW9PPQS0CBKV"},
31
+ # {:status=>"InProgress", :aws_id=>"I1HV23N5KD3XH9"}]
32
+ #
33
+ def list_invalidations(distribution_aws_id)
34
+ result = []
35
+ incrementally_list_invalidations(distribution_aws_id) do |response|
36
+ result += response[:invalidations]
37
+ true
38
+ end
39
+ result
40
+ end
41
+
42
+ # Incrementally list Invalidations.
43
+ # Optional params: +:marker+ and +:max_items+.
44
+ #
45
+ def incrementally_list_invalidations(distribution_aws_id, params={}, &block)
46
+ opts = {}
47
+ opts['MaxItems'] = params[:max_items] if params[:max_items]
48
+ opts['Marker'] = params[:marker] if params[:marker]
49
+ last_response = nil
50
+ loop do
51
+ link = generate_request('GET', "distribution/#{distribution_aws_id}/invalidation", opts)
52
+ last_response = request_info(link, AcfInvalidationsListParser.new(:logger => @logger))
53
+ opts['Marker'] = last_response[:next_marker]
54
+ break unless block && block.call(last_response) && !last_response[:next_marker].right_blank?
55
+ end
56
+ last_response
57
+ end
58
+
59
+ #-----------------------------------------------------------------
60
+ # Origin Access Identity
61
+ #-----------------------------------------------------------------
62
+
63
+ # Create a new Invalidation batch.
64
+ #
65
+ # acf.create_invalidation('E3LTBMK4EAQS7D', :path => ['/boot.jpg', '/kd/boot.public.1.jpg']) #=>
66
+ # {:status=>"InProgress",
67
+ # :create_time=>"2010-12-08T14:03:38.449Z",
68
+ # :location=> "https://cloudfront.amazonaws.com/2010-11-01/distribution/E3LTBMK4EAQS7D/invalidation/I3AW9PPQS0CBKV",
69
+ # :aws_id=>"I3AW9PPQS0CBKV",
70
+ # :invalidation_batch=>
71
+ # {:caller_reference=>"201012081703372555972012",
72
+ # :path=>["/boot.jpg", "/kd/boot.public.1.jpg"]}}
73
+ #
74
+ def create_invalidation(distribution_aws_id, invalidation_batch)
75
+ invalidation_batch[:caller_reference] ||= generate_call_reference
76
+ link = generate_request('POST', "/distribution/#{distribution_aws_id}/invalidation", {}, invalidation_batch_to_xml(invalidation_batch))
77
+ merge_headers(request_info(link, AcfInvalidationsListParser.new(:logger => @logger))[:invalidations].first)
78
+ end
79
+
80
+ # Get Invalidation
81
+ #
82
+ # acf.get_invalidation('E3LTBMK4EAQS7D', 'I3AW9PPQS0CBKV') #=>
83
+ # {:create_time=>"2010-12-08T14:03:38.449Z",
84
+ # :status=>"InProgress",
85
+ # :aws_id=>"I3AW9PPQS0CBKV",
86
+ # :invalidation_batch=>
87
+ # {:caller_reference=>"201012081703372555972012",
88
+ # :path=>["/boot.jpg", "/kd/boot.public.1.jpg"]}}
89
+ #
90
+ def get_invalidation(distribution_aws_id, aws_id)
91
+ link = generate_request('GET', "distribution/#{distribution_aws_id}/invalidation/#{aws_id}")
92
+ merge_headers(request_info(link, AcfInvalidationsListParser.new(:logger => @logger))[:invalidations].first)
93
+ end
94
+
95
+ #-----------------------------------------------------------------
96
+ # Batch
97
+ #-----------------------------------------------------------------
98
+
99
+ def invalidation_batch_to_xml(invalidation_batch) # :nodoc:
100
+ paths = ''
101
+ Array(invalidation_batch[:path]).each do |path|
102
+ paths << " <Path>#{AwsUtils::xml_escape(path)}</Path>\n"
103
+ end
104
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
105
+ "<InvalidationBatch xmlns=\"http://#{@params[:server]}/doc/#{API_VERSION}/\">\n" +
106
+ " <CallerReference>#{invalidation_batch[:caller_reference]}</CallerReference>\n" +
107
+ paths +
108
+ "</InvalidationBatch>"
109
+ end
110
+
111
+ #-----------------------------------------------------------------
112
+ # PARSERS:
113
+ #-----------------------------------------------------------------
114
+
115
+ class AcfInvalidationsListParser < RightAWSParser # :nodoc:
116
+ def reset
117
+ @result = { :invalidations => [] }
118
+ end
119
+ def tagstart(name, attributes)
120
+ case name
121
+ when %r{(InvalidationSummary|Invalidation)$} then @item = {}
122
+ when %r{InvalidationBatch} then @item[:invalidation_batch] = {}
123
+ end
124
+ end
125
+ def tagend(name)
126
+ case name
127
+ when 'Marker' then @result[:marker] = @text
128
+ when 'NextMarker' then @result[:next_marker] = @text
129
+ when 'MaxItems' then @result[:max_items] = @text.to_i
130
+ when 'IsTruncated' then @result[:is_truncated] = (@text == 'true')
131
+ when 'Id' then @item[:aws_id] = @text
132
+ when 'Status' then @item[:status] = @text
133
+ when 'CreateTime' then @item[:create_time] = @text
134
+ when 'Path' then (@item[:invalidation_batch][:path] ||= []) << @text
135
+ when 'CallerReference' then @item[:invalidation_batch][:caller_reference] = @text
136
+ when %r{(InvalidationSummary|Invalidation)$}
137
+ @item[:invalidation_batch][:path].sort! if @item[:invalidation_batch] && !@item[:invalidation_batch][:path].right_blank?
138
+ @result[:invalidations] << @item
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+ end
@@ -85,7 +85,7 @@ module RightAws
85
85
  link = generate_request('GET', 'origin-access-identity/cloudfront', opts)
86
86
  last_response = request_info(link, AcfOriginAccesIdentitiesListParser.new(:logger => @logger))
87
87
  opts['Marker'] = last_response[:next_marker]
88
- break unless block && block.call(last_response) && !last_response[:next_marker].blank?
88
+ break unless block && block.call(last_response) && !last_response[:next_marker].right_blank?
89
89
  end
90
90
  last_response
91
91
  end
@@ -159,7 +159,7 @@ module RightAws
159
159
  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
160
160
  "<CloudFrontOriginAccessIdentityConfig xmlns=\"http://#{@params[:server]}/doc/#{API_VERSION}/\">\n" +
161
161
  " <CallerReference>#{config[:caller_reference]}</CallerReference>\n" +
162
- " <Comment>#{AcfInterface::escape(config[:comment].to_s)}</Comment>\n" +
162
+ " <Comment>#{AwsUtils::xml_escape(config[:comment].to_s)}</Comment>\n" +
163
163
  "</CloudFrontOriginAccessIdentityConfig>"
164
164
  end
165
165
 
@@ -214,8 +214,8 @@ module RightAws
214
214
  when 'IsTruncated' then @result[:is_truncated] = (@text == 'true')
215
215
  when 'Id' then @item[:aws_id] = @text
216
216
  when 'S3CanonicalUserId' then @item[:s3_canonical_user_id] = @text
217
- when 'CallerReference' then @item[:caller_reference] = @text
218
- when 'Comment' then @item[:comment] = AcfInterface::unescape(@text)
217
+ when 'CallerReference' then @item[:caller_reference] = @text
218
+ when 'Comment' then @item[:comment] = AwsUtils::xml_unescape(@text)
219
219
  end
220
220
  case full_tag_name
221
221
  when %r{CloudFrontOriginAccessIdentitySummary$},
@@ -40,7 +40,7 @@ module RightAws
40
40
  # :aws_id=>"E3CWE2Z9USOS6B",
41
41
  # :enabled=>true,
42
42
  # :domain_name=>"s2jz1ourvss1fj.cloudfront.net",
43
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
43
+ # :s3_origin=> {:dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com"},
44
44
  # :last_modified_time=>"2010-04-19T08:53:32.574Z",
45
45
  # :comment=>"Woo-Hoo!",
46
46
  # :cnames=>["stream.web.my-awesome-site.net"]},
@@ -49,7 +49,7 @@ module RightAws
49
49
  # :aws_id=>"E3NPQZY4LKAYQ8",
50
50
  # :enabled=>true,
51
51
  # :domain_name=>"sw9nrsq9pudk3.cloudfront.net",
52
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
52
+ # :s3_origin=> {:dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com"},
53
53
  # :last_modified_time=>"2010-04-19T08:59:09.600Z",
54
54
  # :comment=>"Woo-Hoo!",
55
55
  # :cnames=>["stream-6.web.my-awesome-site.net"]}]
@@ -78,7 +78,7 @@ module RightAws
78
78
  # :enabled=>true,
79
79
  # :last_modified_time=>"2010-04-19T08:53:32.574Z",
80
80
  # :domain_name=>"s2jz1ourvss1fj.cloudfront.net",
81
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
81
+ # :s3_origin=> {:dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com"},
82
82
  # :comment=>"Woo-Hoo!"}],
83
83
  # :max_items=>1,
84
84
  # :is_truncated=>true}
@@ -101,7 +101,7 @@ module RightAws
101
101
  link = generate_request('GET', 'streaming-distribution', opts)
102
102
  last_response = request_info(link, AcfDistributionListParser.new(:logger => @logger))
103
103
  opts['Marker'] = last_response[:next_marker]
104
- break unless block && block.call(last_response) && !last_response[:next_marker].blank?
104
+ break unless block && block.call(last_response) && !last_response[:next_marker].right_blank?
105
105
  end
106
106
  last_response
107
107
  end
@@ -118,26 +118,18 @@ module RightAws
118
118
  # :e_tag=>"E2588L5QL4BLXH",
119
119
  # :enabled=>true,
120
120
  # :domain_name=>"s1di8imd85wgld.cloudfront.net",
121
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
121
+ # :s3_origin=> {:dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com"},
122
122
  # :last_modified_time=>Mon Apr 19 08:54:42 UTC 2010,
123
123
  # :location=>
124
124
  # "https://cloudfront.amazonaws.com/streaming-distribution/E1M5LERJLU636F",
125
125
  # :comment=>"Woo-Hoo!"}
126
126
  #
127
- def create_streaming_distribution(origin, comment='', enabled=true, cnames=[], caller_reference=nil)
128
- config = { :origin => origin,
129
- :comment => comment,
130
- :enabled => enabled,
131
- :cnames => Array(cnames),
132
- :caller_reference => caller_reference }
133
- create_streaming_distribution_by_config(config)
134
- end
135
-
136
- def create_streaming_distribution_by_config(config)
127
+ def create_streaming_distribution(config)
137
128
  config[:caller_reference] ||= generate_call_reference
138
129
  link = generate_request('POST', 'streaming-distribution', {}, streaming_distribution_config_to_xml(config))
139
130
  merge_headers(request_info(link, AcfDistributionListParser.new(:logger => @logger))[:distributions].first)
140
131
  end
132
+ alias_method :create_streaming_distribution_by_config, :create_streaming_distribution
141
133
 
142
134
  # Get a streaming distribution's information.
143
135
  # Returns a distribution's information or RightAws::AwsError exception.
@@ -149,7 +141,7 @@ module RightAws
149
141
  # :aws_id=>"E3CWE2Z9USOS6B",
150
142
  # :enabled=>true,
151
143
  # :domain_name=>"s2jz1ourvss1fj.cloudfront.net",
152
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
144
+ # :s3_origin=> {:dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com"},
153
145
  # :last_modified_time=>"2010-04-19T08:53:32.574Z",
154
146
  # :comment=>"Woo-Hoo!",
155
147
  # :caller_reference=>"201004191253311625537161"}
@@ -167,10 +159,11 @@ module RightAws
167
159
  # :aws_id=>"E1M5LERJLU636F",
168
160
  # :enabled=>false,
169
161
  # :domain_name=>"s1di8imd85wgld.cloudfront.net",
170
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
162
+ # :s3_origin=> {
163
+ # :dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com",
164
+ # :origin_access_identity=>"origin-access-identity/cloudfront/E3JPJZ80ZBX24G"},
171
165
  # :last_modified_time=>"2010-04-19T09:14:07.160Z",
172
166
  # :comment=>"Olah-lah!",
173
- # :origin_access_identity=>"origin-access-identity/cloudfront/E3JPJZ80ZBX24G",
174
167
  # :caller_reference=>"201004191254412191173215"}
175
168
  #
176
169
  def get_streaming_distribution(aws_id)
@@ -186,9 +179,10 @@ module RightAws
186
179
  # :e_tag=>"E2K6XD13RCJQ6E",
187
180
  # :cnames=>["stream-1.web.my-awesome-site.net"],
188
181
  # :enabled=>false,
189
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
182
+ # :s3_origin=> {
183
+ # :dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com",
184
+ # :origin_access_identity=>"origin-access-identity/cloudfront/E3JPJZ80ZBX24G",},
190
185
  # :comment=>"Olah-lah!",
191
- # :origin_access_identity=>"origin-access-identity/cloudfront/E3JPJZ80ZBX24G",
192
186
  # :caller_reference=>"201004191254412191173215"}
193
187
  #
194
188
  def get_streaming_distribution_config(aws_id)
@@ -197,21 +191,20 @@ module RightAws
197
191
  end
198
192
 
199
193
  # Set a streaming distribution's configuration
200
- # (the :origin and the :caller_reference cannot be changed).
201
194
  # Returns +true+ on success or RightAws::AwsError exception.
202
195
  #
203
196
  # acf.get_streaming_distribution_config('E1M5LERJLU636F') #=>
204
197
  # {:e_tag=>"E2588L5QL4BLXH",
205
198
  # :cnames=>["stream-1.web.my-awesome-site.net"],
206
199
  # :enabled=>true,
207
- # :origin=>"bucket-for-konstantin-00.s3.amazonaws.com",
200
+ # :s3_origin=> {:dns_name=>"bucket-for-konstantin-00.s3.amazonaws.com"},
208
201
  # :comment=>"Woo-Hoo!",
209
202
  # :caller_reference=>"201004191254412191173215"}
210
203
  #
211
- # config[:comment] = 'Olah-lah!'
212
- # config[:enabled] = false
213
- # config[:origin_access_identity] = "origin-access-identity/cloudfront/E3JPJZ80ZBX24G"
214
- # config[:trusted_signers] = ['self', '648772220000', '120288270000']
204
+ # config[:comment] = 'Olah-lah!'
205
+ # config[:enabled] = false
206
+ # config[:s3_origin][:origin_access_identity] = "origin-access-identity/cloudfront/E3JPJZ80ZBX24G"
207
+ # config[:trusted_signers] = ['self', '648772220000', '120288270000']
215
208
  #
216
209
  # acf.set_distribution_config('E2REJM3VUN5RSI', config) #=> true
217
210
  #
@@ -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
@@ -145,7 +144,7 @@ module RightAws
145
144
  period = (options[:period] && options[:period].to_i) || 60
146
145
  # Statistics ('Average' by default)
147
146
  statistics = Array(options[:statistics]).flatten
148
- statistics = statistics.blank? ? ['Average'] : statistics.map{|statistic| statistic.to_s.capitalize }
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)
@@ -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.
@@ -154,7 +153,7 @@ module RightAws
154
153
  options[:max_size] ||= 20
155
154
  options[:cooldown] ||= 0
156
155
  request_hash = amazonize_list('AvailabilityZones.member', availability_zones)
157
- request_hash.merge!( amazonize_list('LoadBalancerNames', options[:load_balancer_names]) )
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],
@@ -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
@@ -355,10 +354,10 @@ module RightAws
355
354
  request_hash = { 'LaunchConfigurationName' => launch_configuration_name,
356
355
  'ImageId' => image_id,
357
356
  'InstanceType' => instance_type }
358
- request_hash.merge!(amazonize_list('SecurityGroups.member', options[:security_groups])) unless options[:security_groups].blank?
357
+ request_hash.merge!(amazonize_list('SecurityGroups.member', options[:security_groups])) unless options[:security_groups].right_blank?
359
358
  request_hash.merge!(amazonize_block_device_mappings(options[:block_device_mappings], 'BlockDeviceMappings.member'))
360
359
  request_hash['KeyName'] = options[:key_name] if options[:key_name]
361
- request_hash['UserData'] = Base64.encode64(options[:user_data]).delete("\n") unless options[:user_data].blank? 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]
362
361
  request_hash['KernelId'] = options[:kernel_id] if options[:kernel_id]
363
362
  request_hash['RamdiskId'] = options[:ramdisk_id] if options[:ramdisk_id]
364
363
  link = generate_request("CreateLaunchConfiguration", request_hash)
@@ -428,7 +427,7 @@ module RightAws
428
427
  link = generate_request("DescribeLaunchConfigurations", request_hash)
429
428
  last_response = request_info( link, DescribeLaunchConfigurationsParser.new(:logger => @logger) )
430
429
  request_hash['NextToken'] = last_response[:next_token]
431
- 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?
432
431
  end
433
432
  last_response
434
433
  end
@@ -23,7 +23,6 @@
23
23
 
24
24
  # Test
25
25
  module RightAws
26
- # require 'md5'
27
26
  require 'digest/md5'
28
27
  require 'pp'
29
28
 
@@ -53,6 +52,14 @@ module RightAws
53
52
  end
54
53
  end
55
54
 
55
+ def self.xml_escape(text) # :nodoc:
56
+ REXML::Text::normalize(text)
57
+ end
58
+
59
+ def self.xml_unescape(text) # :nodoc:
60
+ REXML::Text::unnormalize(text)
61
+ end
62
+
56
63
  # Set a timestamp and a signature version
57
64
  def self.fix_service_params(service_hash, signature)
58
65
  service_hash["Timestamp"] ||= utc_iso8601(Time.now) unless service_hash["Expires"]
@@ -136,6 +143,20 @@ module RightAws
136
143
  params = items.last.kind_of?(Hash) ? items.pop : {}
137
144
  [items, params]
138
145
  end
146
+
147
+ # Generates a token in format of:
148
+ # 1. "1dd8d4e4-db6b-11df-b31d-0025b37efad0 (if UUID gem is loaded)
149
+ # 2. "1287483761-855215-zSv2z-bWGj2-31M5t-ags9m" (if UUID gem is not loaded)
150
+ TOKEN_GENERATOR_CHARSET = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
151
+ def self.generate_unique_token
152
+ time = Time.now
153
+ token = "%d-%06d" % [time.to_i, time.usec]
154
+ 4.times do
155
+ token << "-"
156
+ 5.times { token << TOKEN_GENERATOR_CHARSET[rand(TOKEN_GENERATOR_CHARSET.size)] }
157
+ end
158
+ token
159
+ end
139
160
  end
140
161
 
141
162
  class AwsBenchmarkingBlock #:nodoc:
@@ -181,7 +202,31 @@ module RightAws
181
202
  def self.amazon_problems=(problems_list)
182
203
  @@amazon_problems = problems_list
183
204
  end
184
-
205
+
206
+ # Raise an exception if a timeout occures while an API call is in progress.
207
+ # This helps to avoid a duplicate resources creation when Amazon hangs for some time and
208
+ # RightHttpConnection is forced to use retries to get a response from it.
209
+ #
210
+ # If an API call action is in the list then no attempts to retry are performed.
211
+ #
212
+ RAISE_ON_TIMEOUT_ON_ACTIONS = %w{
213
+ AllocateAddress
214
+ CreateSnapshot
215
+ CreateVolume
216
+ PurchaseReservedInstancesOffering
217
+ RequestSpotInstances
218
+ RunInstances
219
+ }
220
+ @@raise_on_timeout_on_actions = RAISE_ON_TIMEOUT_ON_ACTIONS.dup
221
+
222
+ def self.raise_on_timeout_on_actions
223
+ @@raise_on_timeout_on_actions
224
+ end
225
+
226
+ def self.raise_on_timeout_on_actions=(actions_list)
227
+ @@raise_on_timeout_on_actions = actions_list
228
+ end
229
+
185
230
  end
186
231
 
187
232
  module RightAwsBaseInterface
@@ -222,9 +267,9 @@ module RightAws
222
267
  @params = params
223
268
  # If one defines EC2_URL he may forget to use a single slash as an "empty service" path.
224
269
  # Amazon does not like this therefore add this bad boy if he is missing...
225
- service_info[:default_service] = '/' if service_info[:default_service].blank?
270
+ service_info[:default_service] = '/' if service_info[:default_service].right_blank?
226
271
  raise AwsError.new("AWS access keys are required to operate on #{service_info[:name]}") \
227
- if aws_access_key_id.blank? || aws_secret_access_key.blank?
272
+ if aws_access_key_id.right_blank? || aws_secret_access_key.right_blank?
228
273
  @aws_access_key_id = aws_access_key_id
229
274
  @aws_secret_access_key = aws_secret_access_key
230
275
  # if the endpoint was explicitly defined - then use it
@@ -233,7 +278,7 @@ module RightAws
233
278
  @params[:port] = URI.parse(@params[:endpoint_url]).port
234
279
  @params[:service] = URI.parse(@params[:endpoint_url]).path
235
280
  # make sure the 'service' path is not empty
236
- @params[:service] = service_info[:default_service] if @params[:service].blank?
281
+ @params[:service] = service_info[:default_service] if @params[:service].right_blank?
237
282
  @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
238
283
  @params[:region] = nil
239
284
  else
@@ -243,12 +288,15 @@ module RightAws
243
288
  @params[:service] ||= service_info[:default_service]
244
289
  @params[:protocol] ||= service_info[:default_protocol]
245
290
  end
246
- # @params[:multi_thread] ||= defined?(AWS_DAEMON)
291
+ # a set of options to be passed to RightHttpConnection object
292
+ @params[:connection_options] = {} unless @params[:connection_options].is_a?(Hash)
293
+ @with_connection_options = {}
247
294
  @params[:connections] ||= :shared # || :dedicated
248
295
  @params[:max_connections] ||= 10
249
296
  @params[:connection_lifetime] ||= 20*60
250
297
  @params[:api_version] ||= service_info[:default_api_version]
251
298
  @logger = @params[:logger]
299
+ @logger = ::Rails.logger if !@logger && defined?(::Rails) && ::Rails.respond_to?(:logger)
252
300
  @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
253
301
  @logger = Logger.new(STDOUT) if !@logger
254
302
  @logger.info "New #{self.class.name} using #{@params[:connections]} connections mode"
@@ -314,11 +362,57 @@ module RightAws
314
362
  raise if $!.is_a?(AwsNoChange)
315
363
  AwsError::on_aws_exception(self, options)
316
364
  end
317
-
318
- # # Return +true+ if this instance works in multi_thread mode and +false+ otherwise.
319
- # def multi_thread
320
- # @params[:multi_thread]
321
- # end
365
+
366
+ #----------------------------
367
+ # HTTP Connections handling
368
+ #----------------------------
369
+
370
+ def get_server_url(request) # :nodoc:
371
+ "#{request[:protocol]}://#{request[:server]}:#{request[:port]}"
372
+ end
373
+
374
+ def get_connections_storage(aws_service) # :nodoc:
375
+ case @params[:connections].to_s
376
+ when 'dedicated' then @connections_storage ||= {}
377
+ else Thread.current[aws_service] ||= {}
378
+ end
379
+ end
380
+
381
+ def destroy_connection(request, reason) # :nodoc:
382
+ connections = get_connections_storage(request[:aws_service])
383
+ server_url = get_server_url(request)
384
+ if connections[server_url]
385
+ connections[server_url][:connection].finish(reason)
386
+ connections.delete(server_url)
387
+ end
388
+ end
389
+
390
+ # Expire the connection if it has expired.
391
+ def get_connection(request) # :nodoc:
392
+ server_url = get_server_url(request)
393
+ connection_storage = get_connections_storage(request[:aws_service])
394
+ life_time_scratch = Time.now-@params[:connection_lifetime]
395
+ # Delete out-of-dated connections
396
+ connections_in_list = 0
397
+ connection_storage.to_a.sort{|conn1, conn2| conn2[1][:last_used_at] <=> conn1[1][:last_used_at]}.each do |serv_url, conn_opts|
398
+ if @params[:max_connections] <= connections_in_list
399
+ conn_opts[:connection].finish('out-of-limit')
400
+ connection_storage.delete(server_url)
401
+ elsif conn_opts[:last_used_at] < life_time_scratch
402
+ conn_opts[:connection].finish('out-of-date')
403
+ connection_storage.delete(server_url)
404
+ else
405
+ connections_in_list += 1
406
+ end
407
+ end
408
+ connection = (connection_storage[server_url] ||= {})
409
+ connection[:last_used_at] = Time.now
410
+ connection[:connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
411
+ end
412
+
413
+ #----------------------------
414
+ # HTTP Requests handling
415
+ #----------------------------
322
416
 
323
417
  # ACF, AMS, EC2, LBS and SDB uses this guy
324
418
  # SQS and S3 use their own methods
@@ -354,43 +448,26 @@ module RightAws
354
448
  raise "Unsupported HTTP verb #{verb.inspect}!"
355
449
  end
356
450
  # prepare output hash
357
- { :request => request,
358
- :server => @params[:server],
359
- :port => @params[:port],
360
- :protocol => @params[:protocol] }
361
- end
362
-
363
- def get_connection(aws_service, request) #:nodoc
364
- server_url = "#{request[:protocol]}://#{request[:server]}:#{request[:port]}}"
365
- #
366
- case @params[:connections].to_s
367
- when 'dedicated'
368
- @connections_storage ||= {}
369
- else # 'dedicated'
370
- @connections_storage = (Thread.current[aws_service] ||= {})
371
- end
372
- #
373
- @connections_storage[server_url] ||= {}
374
- @connections_storage[server_url][:last_used_at] = Time.now
375
- @connections_storage[server_url][:connection] ||= Rightscale::HttpConnection.new(:exception => RightAws::AwsError, :logger => @logger)
376
- # keep X most recent connections (but were used not far than Y minutes ago)
377
- connections = 0
378
- @connections_storage.to_a.sort{|i1, i2| i2[1][:last_used_at] <=> i1[1][:last_used_at]}.to_a.each do |i|
379
- if i[0] != server_url && (@params[:max_connections] <= connections || i[1][:last_used_at] < Time.now - @params[:connection_lifetime])
380
- # delete the connection from the list
381
- @connections_storage.delete(i[0])
382
- # then finish it
383
- i[1][:connection].finish((@params[:max_connections] <= connections) ? "out-of-limit" : "out-of-date") rescue nil
384
- else
385
- connections += 1
386
- end
451
+ request_hash = { :request => request,
452
+ :server => @params[:server],
453
+ :port => @params[:port],
454
+ :protocol => @params[:protocol] }
455
+ request_hash.merge!(@params[:connection_options])
456
+ request_hash.merge!(@with_connection_options)
457
+
458
+ # If an action is marked as "non-retryable" and there was no :raise_on_timeout option set
459
+ # explicitly then do set that option
460
+ if Array(RightAwsBase::raise_on_timeout_on_actions).include?(action) && !request_hash.has_key?(:raise_on_timeout)
461
+ request_hash.merge!(:raise_on_timeout => true)
387
462
  end
388
- @connections_storage[server_url][:connection]
463
+
464
+ request_hash
389
465
  end
390
466
 
391
467
  # All services uses this guy.
392
468
  def request_info_impl(aws_service, benchblock, request, parser, &block) #:nodoc:
393
- @connection = get_connection(aws_service, request)
469
+ request[:aws_service] = aws_service
470
+ @connection = get_connection(request)
394
471
  @last_request = request[:request]
395
472
  @last_response = nil
396
473
  response = nil
@@ -405,25 +482,31 @@ module RightAws
405
482
  # Exceptions can originate from code directly in the block, or from user
406
483
  # code called in the other block which is passed to response.read_body.
407
484
  benchblock.service.add! do
408
- responsehdr = @connection.request(request) do |response|
409
- #########
410
- begin
411
- @last_response = response
412
- if response.is_a?(Net::HTTPSuccess)
413
- @error_handler = nil
414
- response.read_body(&block)
415
- else
416
- @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
417
- check_result = @error_handler.check(request)
418
- if check_result
485
+ begin
486
+ responsehdr = @connection.request(request) do |response|
487
+ #########
488
+ begin
489
+ @last_response = response
490
+ if response.is_a?(Net::HTTPSuccess)
419
491
  @error_handler = nil
420
- return check_result
492
+ response.read_body(&block)
493
+ else
494
+ @error_handler = AWSErrorHandler.new(self, parser, :errors_list => self.class.amazon_problems) unless @error_handler
495
+ check_result = @error_handler.check(request)
496
+ if check_result
497
+ @error_handler = nil
498
+ return check_result
499
+ end
500
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
421
501
  end
422
- raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
502
+ rescue Exception => e
503
+ blockexception = e
423
504
  end
424
- rescue Exception => e
425
- blockexception = e
426
505
  end
506
+ rescue Exception => e
507
+ # Kill a connection if we run into a low level connection error
508
+ destroy_connection(request, "error: #{e.message}")
509
+ raise e
427
510
  end
428
511
  #########
429
512
 
@@ -437,7 +520,15 @@ module RightAws
437
520
  return parser.result
438
521
  end
439
522
  else
440
- benchblock.service.add!{ response = @connection.request(request) }
523
+ benchblock.service.add! do
524
+ begin
525
+ response = @connection.request(request)
526
+ rescue Exception => e
527
+ # Kill a connection if we run into a low level connection error
528
+ destroy_connection(request, "error: #{e.message}")
529
+ raise e
530
+ end
531
+ end
441
532
  # check response for errors...
442
533
  @last_response = response
443
534
  if response.is_a?(Net::HTTPSuccess)
@@ -459,7 +550,7 @@ module RightAws
459
550
  raise
460
551
  end
461
552
 
462
- def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
553
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true, &block) #:nodoc:
463
554
  # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
464
555
  # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
465
556
  # If the caching is enabled and hit then throw AwsNoChange.
@@ -469,7 +560,7 @@ module RightAws
469
560
  cache_hits?(method.to_sym, response.body) if use_cache
470
561
  parser = parser_class.new(:logger => @logger)
471
562
  benchblock.xml.add!{ parser.parse(response, params) }
472
- result = block_given? ? yield(parser) : parser.result
563
+ result = block ? block.call(parser) : parser.result
473
564
  # update parsed data
474
565
  update_cache(method.to_sym, :parsed => result) if use_cache
475
566
  result
@@ -480,7 +571,24 @@ module RightAws
480
571
  @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}i] && $1
481
572
  end
482
573
 
574
+ # Incrementally lists something.
575
+ def incrementally_list_items(action, parser_class, params={}, &block) # :nodoc:
576
+ params = params.dup
577
+ params['MaxItems'] = params.delete(:max_items) if params[:max_items]
578
+ params['Marker'] = params.delete(:marker) if params[:marker]
579
+ last_response = nil
580
+ loop do
581
+ last_response = request_info( generate_request(action, params), parser_class.new(:logger => @logger))
582
+ params['Marker'] = last_response[:marker]
583
+ break unless block && block.call(last_response) && !last_response[:marker].right_blank?
584
+ end
585
+ last_response
586
+ end
587
+
483
588
  # Format array of items into Amazons handy hash ('?' is a place holder):
589
+ # Options:
590
+ # :default => "something" : Set a value to "something" when it is nil
591
+ # :default => :skip_nils : Skip nil values
484
592
  #
485
593
  # amazonize_list('Item', ['a', 'b', 'c']) =>
486
594
  # { 'Item.1' => 'a', 'Item.2' => 'b', 'Item.3' => 'c' }
@@ -504,7 +612,7 @@ module RightAws
504
612
  # "Filter.2.Key"=>"B",
505
613
  # "Filter.2.Value.1"=>"ba",
506
614
  # "Filter.2.Value.2"=>"bb"}
507
- def amazonize_list(masks, list) #:nodoc:
615
+ def amazonize_list(masks, list, options={}) #:nodoc:
508
616
  groups = {}
509
617
  Array(list).each_with_index do |list_item, i|
510
618
  Array(masks).each_with_index do |mask, mask_idx|
@@ -512,8 +620,16 @@ module RightAws
512
620
  key.sub!('?', (i+1).to_s)
513
621
  value = Array(list_item)[mask_idx]
514
622
  if value.is_a?(Array)
515
- groups.merge!(amazonize_list(key, value))
623
+ groups.merge!(amazonize_list(key, value, options))
516
624
  else
625
+ if value.nil?
626
+ next if options[:default] == :skip_nils
627
+ value = options[:default]
628
+ end
629
+ # Hack to avoid having unhandled '?' in keys : do replace them all with '1':
630
+ # bad: ec2.amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], { a: => :b }) => {"Filter.1.Key"=>:a, "Filter.1.Value.?"=>1}
631
+ # good: ec2.amazonize_list(['Filter.?.Key', 'Filter.?.Value.?'], { a: => :b }) => {"Filter.1.Key"=>:a, "Filter.1.Value.1"=>1}
632
+ key.gsub!('?', '1')
517
633
  groups[key] = value
518
634
  end
519
635
  end
@@ -531,7 +647,7 @@ module RightAws
531
647
 
532
648
  def amazonize_block_device_mappings(block_device_mappings, key = 'BlockDeviceMapping') # :nodoc:
533
649
  result = {}
534
- unless block_device_mappings.blank?
650
+ unless block_device_mappings.right_blank?
535
651
  block_device_mappings = [block_device_mappings] unless block_device_mappings.is_a?(Array)
536
652
  block_device_mappings.each_with_index do |b, idx|
537
653
  BLOCK_DEVICE_KEY_MAPPING.each do |local_name, remote_name|
@@ -546,6 +662,64 @@ module RightAws
546
662
  result
547
663
  end
548
664
 
665
+ # Execute a block of code with custom set of settings for right_http_connection.
666
+ # Accepts next options (see Rightscale::HttpConnection for explanation):
667
+ # :raise_on_timeout
668
+ # :http_connection_retry_count
669
+ # :http_connection_open_timeout
670
+ # :http_connection_read_timeout
671
+ # :http_connection_retry_delay
672
+ # :user_agent
673
+ # :exception
674
+ #
675
+ # Example #1:
676
+ #
677
+ # # Try to create a snapshot but stop with exception if timeout is received
678
+ # # to avoid having a duplicate API calls that create duplicate snapshots.
679
+ # ec2 = Rightscale::Ec2::new(aws_access_key_id, aws_secret_access_key)
680
+ # ec2.with_connection_options(:raise_on_timeout => true) do
681
+ # ec2.create_snapshot('vol-898a6fe0', 'KD: WooHoo!!')
682
+ # end
683
+ #
684
+ # Example #2:
685
+ #
686
+ # # Opposite case when the setting is global:
687
+ # @ec2 = Rightscale::Ec2::new(aws_access_key_id, aws_secret_access_key,
688
+ # :connection_options => { :raise_on_timeout => true })
689
+ # # Create an SSHKey but do tries on timeout
690
+ # ec2.with_connection_options(:raise_on_timeout => false) do
691
+ # new_key = ec2.create_key_pair('my_test_key')
692
+ # end
693
+ #
694
+ # Example #3:
695
+ #
696
+ # # Global settings (HttpConnection level):
697
+ # Rightscale::HttpConnection::params[:http_connection_open_timeout] = 5
698
+ # Rightscale::HttpConnection::params[:http_connection_read_timeout] = 250
699
+ # Rightscale::HttpConnection::params[:http_connection_retry_count] = 2
700
+ #
701
+ # # Local setings (RightAws level)
702
+ # ec2 = Rightscale::Ec2::new(AWS_ID, AWS_KEY,
703
+ # :region => 'us-east-1',
704
+ # :connection_options => {
705
+ # :http_connection_read_timeout => 2,
706
+ # :http_connection_retry_count => 5,
707
+ # :user_agent => 'Mozilla 4.0'
708
+ # })
709
+ #
710
+ # # Custom settings (API call level)
711
+ # ec2.with_connection_options(:raise_on_timeout => true,
712
+ # :http_connection_read_timeout => 10,
713
+ # :user_agent => '') do
714
+ # pp ec2.describe_images
715
+ # end
716
+ #
717
+ def with_connection_options(options, &block)
718
+ @with_connection_options = options
719
+ block.call self
720
+ ensure
721
+ @with_connection_options = {}
722
+ end
549
723
  end
550
724
 
551
725
 
@@ -666,7 +840,7 @@ module RightAws
666
840
  @reiteration_delay = @@reiteration_start_delay
667
841
  @retries = 0
668
842
  # close current HTTP(S) connection on 5xx, errors from list and 4xx errors
669
- @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
843
+ @close_on_error = params[:close_on_error].nil? ? @@close_on_error : params[:close_on_error]
670
844
  @close_on_4xx_probability = params[:close_on_4xx_probability] || @@close_on_4xx_probability
671
845
  end
672
846
 
@@ -709,10 +883,17 @@ module RightAws
709
883
  # Ok, it is a redirect, find the new destination location
710
884
  if redirect_detected
711
885
  location = response['location']
886
+ # As for 301 ( Moved Permanently) Amazon does not return a 'Location' header but
887
+ # it is possible to extract a new endpoint from the response body
888
+ if location.right_blank? && response.code=='301' && response.body
889
+ new_endpoint = response.body[/<Endpoint>(.*?)<\/Endpoint>/] && $1
890
+ location = "#{request[:protocol]}://#{new_endpoint}:#{request[:port]}#{request[:request].path}"
891
+ end
712
892
  # ... log information and ...
713
893
  @aws.logger.info("##### #{@aws.class.name} redirect requested: #{response.code} #{response.message} #####")
714
894
  @aws.logger.info(" Old location: #{request_text_data}")
715
895
  @aws.logger.info(" New location: #{location}")
896
+ @aws.logger.info(" Request Verb: #{request[:request].class.name}")
716
897
  # ... fix the connection data
717
898
  request[:server] = URI.parse(location).host
718
899
  request[:protocol] = URI.parse(location).scheme
@@ -735,7 +916,7 @@ module RightAws
735
916
  # It may have a chance that one server is a semi-down and reconnection
736
917
  # will help us to connect to the other server
737
918
  if !redirect_detected && @close_on_error
738
- @aws.connection.finish "#{self.class.name}: error match to pattern '#{error_match}'"
919
+ @aws.destroy_connection(request, "#{self.class.name}: error match to pattern '#{error_match}'")
739
920
  end
740
921
 
741
922
  if (Time.now < @stop_at)
@@ -764,13 +945,13 @@ module RightAws
764
945
  end
765
946
  # aha, this is unhandled error:
766
947
  elsif @close_on_error
767
- # Is this a 5xx error ?
768
- if @aws.last_response.code.to_s[/^5\d\d$/]
769
- @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'"
948
+ # On 5xx(Server errors), 403(RequestTimeTooSkewed) and 408(Request Timeout) a conection has to be closed
949
+ if @aws.last_response.code.to_s[/^(5\d\d|403|408)$/]
950
+ @aws.destroy_connection(request, "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}'")
770
951
  # Is this a 4xx error ?
771
952
  elsif @aws.last_response.code.to_s[/^4\d\d$/] && @close_on_4xx_probability > rand(100)
772
- @aws.connection.finish "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
773
- "probability: #{@close_on_4xx_probability}%"
953
+ @aws.destroy_connection(request, "#{self.class.name}: code: #{@aws.last_response.code}: '#{@aws.last_response.message}', " +
954
+ "probability: #{@close_on_4xx_probability}%")
774
955
  end
775
956
  end
776
957
  result
@@ -781,29 +962,41 @@ module RightAws
781
962
 
782
963
  #-----------------------------------------------------------------
783
964
 
784
- class RightSaxParserCallback #:nodoc:
785
- def self.include_callback
786
- include XML::SaxParser::Callbacks
787
- end
965
+ class RightSaxParserCallbackTemplate #:nodoc:
788
966
  def initialize(right_aws_parser)
789
967
  @right_aws_parser = right_aws_parser
790
968
  end
791
- def on_start_element(name, attr_hash)
792
- @right_aws_parser.tag_start(name, attr_hash)
793
- end
794
969
  def on_characters(chars)
795
970
  @right_aws_parser.text(chars)
796
971
  end
797
- def on_end_element(name)
798
- @right_aws_parser.tag_end(name)
799
- end
800
972
  def on_start_document; end
801
973
  def on_comment(msg); end
802
974
  def on_processing_instruction(target, data); end
803
975
  def on_cdata_block(cdata); end
804
976
  def on_end_document; end
805
977
  end
806
-
978
+
979
+ class RightSaxParserCallback < RightSaxParserCallbackTemplate
980
+ def self.include_callback
981
+ include XML::SaxParser::Callbacks
982
+ end
983
+ def on_start_element(name, attr_hash)
984
+ @right_aws_parser.tag_start(name, attr_hash)
985
+ end
986
+ def on_end_element(name)
987
+ @right_aws_parser.tag_end(name)
988
+ end
989
+ end
990
+
991
+ class RightSaxParserCallbackNs < RightSaxParserCallbackTemplate
992
+ def on_start_element_ns(name, attr_hash, prefix, uri, namespaces)
993
+ @right_aws_parser.tag_start(name, attr_hash)
994
+ end
995
+ def on_end_element_ns(name, prefix, uri)
996
+ @right_aws_parser.tag_end(name)
997
+ end
998
+ end
999
+
807
1000
  class RightAWSParser #:nodoc:
808
1001
  # default parsing library
809
1002
  DEFAULT_XML_LIBRARY = 'rexml'
@@ -864,32 +1057,35 @@ module RightAws
864
1057
  if @xml_lib=='libxml' && !defined?(XML::SaxParser)
865
1058
  begin
866
1059
  require 'xml/libxml'
867
- # is it new ? - Setup SaxParserCallback
868
- if XML::Parser::VERSION >= '0.5.1.0'
869
- RightSaxParserCallback.include_callback
1060
+ # Setup SaxParserCallback
1061
+ if XML::Parser::VERSION >= '0.5.1' &&
1062
+ XML::Parser::VERSION < '0.9.7'
1063
+ RightSaxParserCallback.include_callback
870
1064
  end
871
1065
  rescue LoadError => e
872
- @@supported_xml_libs.delete(@xml_lib)
873
- @xml_lib = DEFAULT_XML_LIBRARY
1066
+ @@supported_xml_libs.delete(@xml_lib)
1067
+ @xml_lib = DEFAULT_XML_LIBRARY
874
1068
  if @logger
875
1069
  @logger.error e.inspect
876
1070
  @logger.error e.backtrace
877
- @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
1071
+ @logger.info "Can not load 'libxml' library. '#{DEFAULT_XML_LIBRARY}' is used for parsing."
878
1072
  end
879
1073
  end
880
1074
  end
881
1075
  # Parse the xml text
882
1076
  case @xml_lib
883
- when 'libxml'
1077
+ when 'libxml'
884
1078
  if XML::Parser::VERSION >= '0.9.9'
885
1079
  # avoid warning on every usage
886
1080
  xml = XML::SaxParser.string(xml_text)
887
1081
  else
888
- xml = XML::SaxParser.new
1082
+ xml = XML::SaxParser.new
889
1083
  xml.string = xml_text
890
1084
  end
891
1085
  # check libxml-ruby version
892
- if XML::Parser::VERSION >= '0.5.1.0'
1086
+ if XML::Parser::VERSION >= '0.9.7'
1087
+ xml.callbacks = RightSaxParserCallbackNs.new(self)
1088
+ elsif XML::Parser::VERSION >= '0.5.1'
893
1089
  xml.callbacks = RightSaxParserCallback.new(self)
894
1090
  else
895
1091
  xml.on_start_element{|name, attr_hash| self.tag_start(name, attr_hash)}