staugaard-cloudmaster 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/VERSION.yml +4 -0
  2. data/bin/cloudmaster +45 -0
  3. data/lib/AWS/AWS.rb +3 -0
  4. data/lib/AWS/EC2.rb +14 -0
  5. data/lib/AWS/S3.rb +14 -0
  6. data/lib/AWS/SQS.rb +14 -0
  7. data/lib/AWS/SimpleDB.rb +14 -0
  8. data/lib/MockAWS/EC2.rb +119 -0
  9. data/lib/MockAWS/S3.rb +39 -0
  10. data/lib/MockAWS/SQS.rb +82 -0
  11. data/lib/MockAWS/SimpleDB.rb +46 -0
  12. data/lib/MockAWS/clock.rb +67 -0
  13. data/lib/OriginalAWS/AWS.rb +475 -0
  14. data/lib/OriginalAWS/EC2.rb +783 -0
  15. data/lib/OriginalAWS/S3.rb +559 -0
  16. data/lib/OriginalAWS/SQS.rb +159 -0
  17. data/lib/OriginalAWS/SimpleDB.rb +460 -0
  18. data/lib/RetryAWS/EC2.rb +88 -0
  19. data/lib/RetryAWS/S3.rb +77 -0
  20. data/lib/RetryAWS/SQS.rb +109 -0
  21. data/lib/RetryAWS/SimpleDB.rb +118 -0
  22. data/lib/SafeAWS/EC2.rb +63 -0
  23. data/lib/SafeAWS/S3.rb +56 -0
  24. data/lib/SafeAWS/SQS.rb +75 -0
  25. data/lib/SafeAWS/SimpleDB.rb +88 -0
  26. data/lib/aws_context.rb +165 -0
  27. data/lib/basic_configuration.rb +120 -0
  28. data/lib/clock.rb +10 -0
  29. data/lib/factory.rb +14 -0
  30. data/lib/file_logger.rb +36 -0
  31. data/lib/inifile.rb +148 -0
  32. data/lib/instance_logger.rb +25 -0
  33. data/lib/logger_factory.rb +38 -0
  34. data/lib/periodic.rb +29 -0
  35. data/lib/string_logger.rb +29 -0
  36. data/lib/sys_logger.rb +40 -0
  37. data/lib/user_data.rb +30 -0
  38. data/test/aws-config.ini +9 -0
  39. data/test/cloudmaster-tests.rb +329 -0
  40. data/test/configuration-test.rb +62 -0
  41. data/test/daytime-policy-tests.rb +47 -0
  42. data/test/enumerator-test.rb +47 -0
  43. data/test/fixed-policy-tests.rb +50 -0
  44. data/test/instance-pool-test.rb +359 -0
  45. data/test/instance-test.rb +98 -0
  46. data/test/job-policy-test.rb +95 -0
  47. data/test/manual-policy-tests.rb +63 -0
  48. data/test/named-queue-test.rb +90 -0
  49. data/test/resource-policy-tests.rb +126 -0
  50. data/test/suite +17 -0
  51. data/test/test-config.ini +47 -0
  52. metadata +111 -0
@@ -0,0 +1,159 @@
1
+ # Sample Ruby code for the O'Reilly book "Programming Amazon Web
2
+ # Services" by James Murty.
3
+ #
4
+ # This code was written for Ruby version 1.8.6 or greater.
5
+ #
6
+ # The SQS module implements the Query API of the Amazon Simple Queue
7
+ # Service.
8
+ require 'AWS'
9
+
10
+ class SQS
11
+ include AWS # Include the AWS module as a mixin
12
+
13
+ ENDPOINT_URI = URI.parse("https://queue.amazonaws.com/")
14
+ API_VERSION = '2008-01-01'
15
+ SIGNATURE_VERSION = '1'
16
+
17
+ HTTP_METHOD = 'POST' # 'GET'
18
+
19
+
20
+ def list_queues(queue_name_prefix=nil)
21
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
22
+ {
23
+ 'Action' => 'ListQueues',
24
+ 'QueueNamePrefix' => queue_name_prefix
25
+ })
26
+
27
+ response = do_query(HTTP_METHOD, ENDPOINT_URI, parameters)
28
+
29
+ queue_names = []
30
+ xml_doc = REXML::Document.new(response.body)
31
+
32
+ xml_doc.elements.each('//QueueUrl') do |queue_url|
33
+ queue_names << queue_url.text
34
+ end
35
+
36
+ return queue_names
37
+ end
38
+
39
+ def create_queue(queue_name, visibility_timeout_secs=nil)
40
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
41
+ {
42
+ 'Action' => 'CreateQueue',
43
+ 'QueueName' => queue_name,
44
+ 'DefaultVisibilityTimeout' => visibility_timeout_secs
45
+ })
46
+
47
+ response = do_query(HTTP_METHOD, ENDPOINT_URI, parameters)
48
+
49
+ xml_doc = REXML::Document.new(response.body)
50
+ return xml_doc.elements['//QueueUrl'].text
51
+ end
52
+
53
+ def delete_queue(queue_url)
54
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
55
+ {
56
+ 'Action' => 'DeleteQueue'
57
+ })
58
+
59
+ response = do_query(HTTP_METHOD, URI.parse(queue_url), parameters)
60
+
61
+ return true
62
+ end
63
+
64
+ def get_queue_attributes(queue_url, attribute='All')
65
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
66
+ {
67
+ 'Action' => 'GetQueueAttributes',
68
+ 'AttributeName' => attribute
69
+ })
70
+
71
+ response = do_query(HTTP_METHOD, URI.parse(queue_url), parameters)
72
+
73
+ attributes = {}
74
+ xml_doc = REXML::Document.new(response.body)
75
+
76
+ xml_doc.elements.each('//Attribute') do |att|
77
+ name = att.elements['Name'].text
78
+ # All currently supported attributes have integer values
79
+ value = att.elements['Value'].text.to_i
80
+
81
+ attributes[name] = value
82
+ end
83
+
84
+ return attributes
85
+ end
86
+
87
+ def set_queue_attribute(queue_url, value, attribute='VisibilityTimeout')
88
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
89
+ {
90
+ 'Action' => 'SetQueueAttributes',
91
+ 'Attribute.Name' => attribute,
92
+ 'Attribute.Value' => value
93
+ })
94
+
95
+ response = do_query(HTTP_METHOD, URI.parse(queue_url), parameters)
96
+ return true
97
+ end
98
+
99
+ def send_message(queue_url, message_body, encode=false)
100
+ message_body = encode_base64(message_body) if encode
101
+
102
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
103
+ {
104
+ 'Action' => 'SendMessage',
105
+ 'MessageBody' => message_body
106
+ })
107
+
108
+ response = do_query(HTTP_METHOD, URI.parse(queue_url), parameters)
109
+
110
+ xml_doc = REXML::Document.new(response.body)
111
+ return xml_doc.elements['//MessageId'].text
112
+ end
113
+
114
+ def receive_messages(queue_url, maximum=1, visibility_timeout=nil)
115
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
116
+ {
117
+ 'Action' => 'ReceiveMessage',
118
+ 'MaxNumberOfMessages' => maximum,
119
+ 'VisibilityTimeout' => visibility_timeout
120
+ })
121
+
122
+ response = do_query(HTTP_METHOD, URI.parse(queue_url), parameters)
123
+ msgs = []
124
+
125
+ xml_doc = REXML::Document.new(response.body)
126
+
127
+ xml_doc.elements.each('//Message') do |msg|
128
+ msgs << {
129
+ :id => msg.elements['MessageId'].text,
130
+ :receipt_handle => msg.elements['ReceiptHandle'].text,
131
+ :md5_of_body => msg.elements['MD5OfBody'].text,
132
+ :body => msg.elements['Body'].text
133
+ }
134
+ end
135
+
136
+ return msgs
137
+ end
138
+
139
+ def delete_message(queue_url, receipt_handle)
140
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
141
+ {
142
+ 'Action' => 'DeleteMessage',
143
+ 'ReceiptHandle' => receipt_handle
144
+ })
145
+
146
+ response = do_query(HTTP_METHOD, URI.parse(queue_url), parameters)
147
+
148
+ return true
149
+ end
150
+
151
+ def receive_new_messages(msgs, queue_url, max_count=1)
152
+ new_msgs = receive_messages(queue_url, max_count)
153
+ new_msgs.each do |new|
154
+ msgs << new unless msgs.find{|old| old[:id] == new[:id]}
155
+ end
156
+ return msgs
157
+ end
158
+
159
+ end
@@ -0,0 +1,460 @@
1
+ # Sample Ruby code for the O'Reilly book "Using AWS Infrastructure
2
+ # Services" by James Murty.
3
+ #
4
+ # This code was written for Ruby version 1.8.6 or greater.
5
+ #
6
+ # The SimpleDB module implements the Query API of the Amazon SimpleDB
7
+ # Service.
8
+
9
+ require 'AWS'
10
+ require 'bigdecimal'
11
+
12
+ class SimpleDB
13
+ include AWS # Include the AWS module as a mixin
14
+
15
+ ENDPOINT_URI = URI.parse("https://sdb.amazonaws.com/")
16
+ API_VERSION = '2007-11-07'
17
+ SIGNATURE_VERSION = '1'
18
+
19
+ HTTP_METHOD = 'POST' # 'GET'
20
+
21
+ attr_reader :prior_box_usage
22
+ attr_reader :total_box_usage
23
+
24
+
25
+ def do_sdb_query(parameters)
26
+ response = do_query(HTTP_METHOD, ENDPOINT_URI, parameters)
27
+ xml_doc = REXML::Document.new(response.body)
28
+
29
+ @total_box_usage = 0 if @total_box_usage.nil?
30
+
31
+ @prior_box_usage = xml_doc.elements['//BoxUsage'].text.to_f
32
+ @total_box_usage += @prior_box_usage
33
+
34
+ return xml_doc
35
+ end
36
+
37
+
38
+ def encode_boolean(value)
39
+ if value
40
+ return '!b'
41
+ else
42
+ return '!B'
43
+ end
44
+ end
45
+
46
+
47
+ def decode_boolean(value_str)
48
+ if value_str == '!B'
49
+ return false
50
+ elsif value_str == '!b'
51
+ return true
52
+ else
53
+ raise "Cannot decode boolean from string: #{value_str}"
54
+ end
55
+ end
56
+
57
+
58
+ def encode_date(value)
59
+ return "!d" + value.getutc.iso8601
60
+ end
61
+
62
+
63
+ def decode_date(value_str)
64
+ if value_str[0..1] == '!d'
65
+ return Time.parse(value_str[2..-1])
66
+ else
67
+ raise "Cannot decode date from string: #{value_str}"
68
+ end
69
+ end
70
+
71
+
72
+ def encode_integer(value, max_digits=18)
73
+ upper_bound = (10 ** max_digits)
74
+
75
+ if value >= upper_bound or value < -upper_bound
76
+ raise "Integer #{value} is outside encoding range (-#{upper_bound} " +
77
+ "to #{upper_bound - 1})"
78
+ end
79
+
80
+ if value < 0
81
+ return "!I" + format("%0#{max_digits}d", upper_bound + value)
82
+ else
83
+ return "!i" + format("%0#{max_digits}d", value)
84
+ end
85
+ end
86
+
87
+
88
+ def decode_integer(value_str)
89
+ if value_str[0..1] == '!I'
90
+ # Encoded value is a negative integer
91
+ max_digits = value_str.size - 2
92
+ upper_bound = (10 ** max_digits)
93
+
94
+ return value_str[2..-1].to_i - upper_bound
95
+ elsif value_str[0..1] == '!i'
96
+ # Encoded value is a positive integer
97
+ return value_str[2..-1].to_i
98
+ else
99
+ raise "Cannot decode integer from string: #{value_str}"
100
+ end
101
+ end
102
+
103
+
104
+ def encode_float(value, max_exp_digits=2, max_precision_digits=15)
105
+ exp_midpoint = (10 ** max_exp_digits) / 2
106
+
107
+ sign, fraction, base, exponent = BigDecimal(value.to_s).split
108
+
109
+ if exponent >= exp_midpoint or exponent < -exp_midpoint
110
+ raise "Exponent #{exponent} is outside encoding range " +
111
+ "(-#{exp_midpoint} " + "to #{exp_midpoint - 1})"
112
+ end
113
+
114
+ if fraction.size > max_precision_digits
115
+ # Round fraction value if it exceeds allowed precision.
116
+ fraction_str = fraction[0...max_precision_digits] + '.' +
117
+ fraction[max_precision_digits..-1]
118
+ fraction = BigDecimal(fraction_str).round(0).split[1]
119
+ elsif fraction.size < max_precision_digits
120
+ # Right-pad fraction with zeros if it is too short.
121
+ fraction = fraction + ('0' * (max_precision_digits - fraction.size))
122
+ end
123
+
124
+ # The zero value is a special case, for which the exponent must be 0
125
+ exponent = -exp_midpoint if value == 0
126
+
127
+ if sign == 1
128
+ return format("!f%0#{max_exp_digits}d", exp_midpoint + exponent) +
129
+ format("!%0#{max_precision_digits}d", fraction.to_i)
130
+ else
131
+ fraction_upper_bound = (10 ** max_precision_digits)
132
+ diff_fraction = fraction_upper_bound - BigDecimal(fraction)
133
+ return format("!F%0#{max_exp_digits}d", exp_midpoint - exponent) +
134
+ format("!%0#{max_precision_digits}d", diff_fraction)
135
+ end
136
+ end
137
+
138
+
139
+ def decode_float(value_str)
140
+ prefix = value_str[0..1]
141
+
142
+ if prefix != '!f' and prefix != '!F'
143
+ raise "Cannot decode float from string: #{value_str}"
144
+ end
145
+
146
+ value_str =~ /![fF]([0-9]+)!([0-9]+)/
147
+ exp_str = $1
148
+ fraction_str = $2
149
+
150
+ max_exp_digits = exp_str.size
151
+ exp_midpoint = (10 ** max_exp_digits) / 2
152
+ max_precision_digits = fraction_str.size
153
+
154
+ if prefix == '!F'
155
+ sign = -1
156
+ exp = exp_midpoint - exp_str.to_i
157
+
158
+ fraction_upper_bound = (10 ** max_precision_digits)
159
+ fraction = fraction_upper_bound - BigDecimal(fraction_str)
160
+ else
161
+ sign = 1
162
+ exp = exp_str.to_i - exp_midpoint
163
+
164
+ fraction = BigDecimal(fraction_str)
165
+ end
166
+
167
+ return sign * "0.#{fraction.to_i}".to_f * (10 ** exp)
168
+ end
169
+
170
+
171
+ def encode_attribute_value(value)
172
+ if value == true or value == false
173
+ return encode_boolean(value)
174
+ elsif value.is_a? Time
175
+ return encode_date(value)
176
+ elsif value.is_a? Integer
177
+ return encode_integer(value)
178
+ elsif value.is_a? Numeric
179
+ return encode_float(value)
180
+ else
181
+ # No type-specific encoding is available, so we simply convert
182
+ # the value to a string.
183
+ return value.to_s
184
+ end
185
+ end
186
+
187
+
188
+ def decode_attribute_value(value_str)
189
+ return '' if value_str.nil?
190
+
191
+ # Check whether the '!' flag is present to indicate an encoded value
192
+ return value_str if value_str[0..0] != '!'
193
+
194
+ prefix = value_str[0..1].downcase
195
+ if prefix == '!b'
196
+ return decode_boolean(value_str)
197
+ elsif prefix == '!d'
198
+ return decode_date(value_str)
199
+ elsif prefix == '!i'
200
+ return decode_integer(value_str)
201
+ elsif prefix == '!f'
202
+ return decode_float(value_str)
203
+ else
204
+ return value_str
205
+ end
206
+ end
207
+
208
+
209
+ def list_domains(max_domains=100)
210
+ more_domains = true
211
+ next_token = nil
212
+ domain_names = []
213
+
214
+ while more_domains
215
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
216
+ {
217
+ 'Action' => 'ListDomains',
218
+ 'MaxNumberOfDomains' => max_domains,
219
+ 'NextToken' => next_token
220
+ })
221
+
222
+ xml_doc = do_sdb_query(parameters)
223
+
224
+ xml_doc.elements.each('//DomainName') do |name|
225
+ domain_names << name.text
226
+ end
227
+
228
+ # If we receive a NextToken element, perform a follow-up operation
229
+ # to retrieve the next set of domain names.
230
+ next_token = xml_doc.elements['//NextToken/text()']
231
+ more_domains = !next_token.nil?
232
+ end
233
+
234
+ return domain_names
235
+ end
236
+
237
+
238
+ def create_domain(domain_name)
239
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
240
+ {
241
+ 'Action' => 'CreateDomain',
242
+ 'DomainName' => domain_name
243
+ })
244
+
245
+ do_sdb_query(parameters)
246
+ return true
247
+ end
248
+
249
+
250
+ def delete_domain(domain_name)
251
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
252
+ {
253
+ 'Action' => 'DeleteDomain',
254
+ 'DomainName' => domain_name
255
+ })
256
+
257
+ do_sdb_query(parameters)
258
+ return true
259
+ end
260
+
261
+
262
+ def build_attribute_params(attributes={}, replace=false)
263
+ attribute_params = {}
264
+ index = 0
265
+
266
+ attributes.each do |attrib_name, attrib_value|
267
+ attrib_value = [attrib_value] if not attrib_value.is_a? Array
268
+
269
+ attrib_value.each do |value|
270
+ attribute_params["Attribute.#{index}.Name"] = attrib_name
271
+ if not value.nil?
272
+ if respond_to? :encode_attribute_value
273
+ # Automatically encode attribute values if the method
274
+ # encode_attribute_value is available in this class
275
+ value = encode_attribute_value(value)
276
+ end
277
+ attribute_params["Attribute.#{index}.Value"] = value
278
+ end
279
+ # Add a Replace parameter for the attribute if the replace flag is set
280
+ attribute_params["Attribute.#{index}.Replace"] = 'true' if replace
281
+ index += 1
282
+ end if attrib_value
283
+ end
284
+
285
+ return attribute_params
286
+ end
287
+
288
+
289
+ def put_attributes(domain_name, item_name, attributes, replace=false)
290
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
291
+ {
292
+ 'Action' => 'PutAttributes',
293
+ 'DomainName' => domain_name,
294
+ 'ItemName' => item_name
295
+ })
296
+
297
+ parameters.merge!(build_attribute_params(attributes, replace))
298
+
299
+ do_sdb_query(parameters)
300
+ return true
301
+ end
302
+
303
+
304
+ def delete_attributes(domain_name, item_name, attributes={})
305
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
306
+ {
307
+ 'Action' => 'DeleteAttributes',
308
+ 'DomainName' => domain_name,
309
+ 'ItemName' => item_name
310
+ })
311
+
312
+ parameters.merge!(build_attribute_params(attributes))
313
+
314
+ do_sdb_query(parameters)
315
+ return true
316
+ end
317
+
318
+
319
+ def get_attributes(domain_name, item_name, attribute_name=nil)
320
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
321
+ {
322
+ 'Action' => 'GetAttributes',
323
+ 'DomainName' => domain_name,
324
+ 'ItemName' => item_name,
325
+ 'AttributeName' => attribute_name
326
+ })
327
+
328
+ xml_doc = do_sdb_query(parameters)
329
+
330
+ attributes = {}
331
+ xml_doc.elements.each('//Attribute') do |attribute_node|
332
+ attr_name = attribute_node.elements['Name'].text
333
+ value = attribute_node.elements['Value'].text
334
+
335
+ if respond_to? :decode_attribute_value
336
+ # Automatically decode attribute values if the method
337
+ # decode_attribute_value is available in this class
338
+ value = decode_attribute_value(value)
339
+ end
340
+
341
+ # An empty attribute value is an empty string, not nil.
342
+ value = '' if value.nil?
343
+
344
+ if attributes.has_key?(attr_name)
345
+ attributes[attr_name] << value
346
+ else
347
+ attributes[attr_name] = [value]
348
+ end
349
+ end
350
+
351
+ if not attribute_name.nil?
352
+ # If a specific attribute was requested, return only the values array
353
+ # for this attribute.
354
+ if not attributes[attribute_name]
355
+ return []
356
+ else
357
+ return attributes[attribute_name]
358
+ end
359
+ else
360
+ return attributes
361
+ end
362
+ end
363
+
364
+
365
+ def query(domain_name, query_expression=nil, options={:fetch_all=>true})
366
+ more_items = true
367
+ next_token = nil
368
+ item_names = []
369
+
370
+ while more_items
371
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
372
+ {
373
+ 'Action' => 'Query',
374
+ 'DomainName' => domain_name,
375
+ 'QueryExpression' => query_expression,
376
+ 'MaxNumberOfItems' => options[:max_items],
377
+ 'NextToken' => next_token
378
+ })
379
+
380
+ xml_doc = do_sdb_query(parameters)
381
+
382
+ xml_doc.elements.each('//ItemName') do |item_name|
383
+ item_names << item_name.text
384
+ end
385
+
386
+ if xml_doc.elements['//NextToken']
387
+ next_token = xml_doc.elements['//NextToken'].text.gsub("\n","")
388
+ more_items = options[:fetch_all]
389
+ else
390
+ more_items = false
391
+ end
392
+ end
393
+
394
+ return item_names
395
+ end
396
+
397
+ # The query with attributes feature was added to the S3 API after the
398
+ # release of "Programming Amazon Web Services" so it is not discussed in
399
+ # the book's text. For more details, see:
400
+ # http://www.jamesmurty.com/2008/09/07/samples-for-simpledb-querywithattributes/
401
+ def query_with_attributes(domain_name, query_expression=nil,
402
+ attribute_names=[], options={:fetch_all=>true})
403
+ more_items = true
404
+ next_token = nil
405
+ items = []
406
+
407
+ while more_items
408
+ parameters = build_query_params(API_VERSION, SIGNATURE_VERSION,
409
+ {
410
+ 'Action' => 'QueryWithAttributes',
411
+ 'DomainName' => domain_name,
412
+ 'QueryExpression' => query_expression,
413
+ 'MaxNumberOfItems' => options[:max_items],
414
+ 'NextToken' => next_token
415
+ },{
416
+ 'AttributeName' => attribute_names,
417
+ })
418
+
419
+ xml_doc = do_sdb_query(parameters)
420
+
421
+ xml_doc.elements.each('//Item') do |item_node|
422
+ item = {'name' => item_node.elements['Name'].text}
423
+
424
+ attributes = {}
425
+ item_node.elements.each('Attribute') do |attribute_node|
426
+ attr_name = attribute_node.elements['Name'].text
427
+ value = attribute_node.elements['Value'].text
428
+
429
+ if respond_to? :decode_attribute_value
430
+ # Automatically decode attribute values if the method
431
+ # decode_attribute_value is available in this class
432
+ value = decode_attribute_value(value)
433
+ end
434
+
435
+ # An empty attribute value is an empty string, not nil.
436
+ value = '' if value.nil?
437
+
438
+ if attributes.has_key?(attr_name)
439
+ attributes[attr_name] << value
440
+ else
441
+ attributes[attr_name] = [value]
442
+ end
443
+ end
444
+
445
+ item['attributes'] = attributes
446
+ items << item
447
+ end
448
+
449
+ if xml_doc.elements['//NextToken']
450
+ next_token = xml_doc.elements['//NextToken'].text.gsub("\n","")
451
+ more_items = options[:fetch_all]
452
+ else
453
+ more_items = false
454
+ end
455
+ end
456
+
457
+ return items
458
+ end
459
+
460
+ end
@@ -0,0 +1,88 @@
1
+ require 'AWS/EC2'
2
+
3
+ # RetryAWS wraps the AWS module in exception catcher blocks, so that any
4
+ # exceptions that are thrown do not affect the caller.
5
+ #
6
+ # The RetryEC2, RetrySQS, and RetryS3 log any errors that they encounter, so
7
+ # that they can be examined later.
8
+ module RetryAWS
9
+ # Wrap EC2 functions that we use.
10
+ # Catch errors and do something reasonable.
11
+ class EC2
12
+ def initialize(access_key, secret_key)
13
+ @ec2 = AWS::EC2.new(access_key, secret_key)
14
+ @@log = STDOUT
15
+ @retry_limit = 16
16
+ end
17
+
18
+ def logger=(logger)
19
+ @@log = logger
20
+ end
21
+
22
+ private
23
+
24
+ def report_error(res)
25
+ @@log.puts "error #{$!}"
26
+ $@.each {|line| @@log.puts " #{line}"}
27
+ res
28
+ end
29
+
30
+ def retry?(err, retry_time)
31
+ if err.response.code.to_i >= 500 && retry_time < @retry_limit
32
+ sleep retry_time
33
+ return retry_time * 2
34
+ end
35
+ nil
36
+ end
37
+
38
+ public
39
+
40
+ def describe_images(options={})
41
+ retry_time = 1
42
+ begin
43
+ @ec2.describe_images(options)
44
+ rescue AWS::ServiceError => err
45
+ retry if retry_time = retry?(err, retry_time)
46
+ report_error []
47
+ rescue
48
+ report_error []
49
+ end
50
+ end
51
+
52
+ def describe_instances(*instance_ids)
53
+ retry_time = 1
54
+ begin
55
+ @ec2.describe_instances(*instance_ids)
56
+ rescue AWS::ServiceError => err
57
+ retry if retry_time = retry?(err, retry_time)
58
+ report_error []
59
+ rescue
60
+ report_error []
61
+ end
62
+ end
63
+
64
+ def run_instances(image_id, min_count=1, max_count=min_count, options={})
65
+ retry_time = 1
66
+ begin
67
+ @ec2.run_instances(image_id, min_count, max_count, options)
68
+ rescue AWS::ServiceError => err
69
+ retry if retry_time = retry?(err, retry_time)
70
+ report_error []
71
+ rescue
72
+ report_error []
73
+ end
74
+ end
75
+
76
+ def terminate_instances(*instance_ids)
77
+ retry_time = 1
78
+ begin
79
+ @ec2.terminate_instances(*instance_ids)
80
+ rescue AWS::ServiceError => err
81
+ retry if retry_time = retry?(err, retry_time)
82
+ report_error []
83
+ rescue
84
+ report_error []
85
+ end
86
+ end
87
+ end
88
+ end