staugaard-cloudmaster 0.1.1

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 (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