right_aws 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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)}