right_aws 1.9.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +164 -13
  2. data/Manifest.txt +28 -1
  3. data/README.txt +12 -10
  4. data/Rakefile +56 -29
  5. data/lib/acf/right_acf_interface.rb +343 -172
  6. data/lib/acf/right_acf_invalidations.rb +144 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +229 -0
  9. data/lib/acw/right_acw_interface.rb +248 -0
  10. data/lib/as/right_as_interface.rb +698 -0
  11. data/lib/awsbase/right_awsbase.rb +755 -115
  12. data/lib/awsbase/support.rb +2 -78
  13. data/lib/awsbase/version.rb +9 -0
  14. data/lib/ec2/right_ec2.rb +274 -1294
  15. data/lib/ec2/right_ec2_ebs.rb +514 -0
  16. data/lib/ec2/right_ec2_images.rb +444 -0
  17. data/lib/ec2/right_ec2_instances.rb +797 -0
  18. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  19. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  20. data/lib/ec2/right_ec2_reserved_instances.rb +243 -0
  21. data/lib/ec2/right_ec2_security_groups.rb +496 -0
  22. data/lib/ec2/right_ec2_spot_instances.rb +422 -0
  23. data/lib/ec2/right_ec2_tags.rb +139 -0
  24. data/lib/ec2/right_ec2_vpc.rb +598 -0
  25. data/lib/ec2/right_ec2_vpc2.rb +382 -0
  26. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  27. data/lib/elb/right_elb_interface.rb +573 -0
  28. data/lib/emr/right_emr_interface.rb +728 -0
  29. data/lib/iam/right_iam_access_keys.rb +71 -0
  30. data/lib/iam/right_iam_groups.rb +195 -0
  31. data/lib/iam/right_iam_interface.rb +341 -0
  32. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  33. data/lib/iam/right_iam_users.rb +251 -0
  34. data/lib/rds/right_rds_interface.rb +1657 -0
  35. data/lib/right_aws.rb +30 -13
  36. data/lib/route_53/right_route_53_interface.rb +641 -0
  37. data/lib/s3/right_s3.rb +108 -41
  38. data/lib/s3/right_s3_interface.rb +349 -118
  39. data/lib/sdb/active_sdb.rb +388 -54
  40. data/lib/sdb/right_sdb_interface.rb +323 -64
  41. data/lib/sns/right_sns_interface.rb +286 -0
  42. data/lib/sqs/right_sqs.rb +1 -2
  43. data/lib/sqs/right_sqs_gen2.rb +73 -17
  44. data/lib/sqs/right_sqs_gen2_interface.rb +146 -73
  45. data/lib/sqs/right_sqs_interface.rb +12 -22
  46. data/right_aws.gemspec +91 -0
  47. data/test/README.mdown +39 -0
  48. data/test/acf/test_right_acf.rb +11 -19
  49. data/test/awsbase/test_helper.rb +2 -0
  50. data/test/awsbase/test_right_awsbase.rb +11 -0
  51. data/test/ec2/test_right_ec2.rb +32 -1
  52. data/test/elb/test_helper.rb +2 -0
  53. data/test/elb/test_right_elb.rb +43 -0
  54. data/test/rds/test_helper.rb +2 -0
  55. data/test/rds/test_right_rds.rb +120 -0
  56. data/test/route_53/fixtures/a_record.xml +18 -0
  57. data/test/route_53/fixtures/alias_record.xml +18 -0
  58. data/test/route_53/test_helper.rb +2 -0
  59. data/test/route_53/test_right_route_53.rb +141 -0
  60. data/test/s3/test_right_s3.rb +176 -42
  61. data/test/s3/test_right_s3_stubbed.rb +6 -4
  62. data/test/sdb/test_active_sdb.rb +120 -19
  63. data/test/sdb/test_batch_put_attributes.rb +54 -0
  64. data/test/sdb/test_right_sdb.rb +71 -16
  65. data/test/sns/test_helper.rb +2 -0
  66. data/test/sns/test_right_sns.rb +153 -0
  67. data/test/sqs/test_right_sqs.rb +0 -6
  68. data/test/sqs/test_right_sqs_gen2.rb +104 -49
  69. data/test/ts_right_aws.rb +1 -0
  70. metadata +181 -22
@@ -0,0 +1,728 @@
1
+ #
2
+ # Copyright (c) 2011 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
+
24
+ module RightAws
25
+
26
+ # = RightAWS::EmrInterface -- RightScale Amazon Elastic Map Reduce interface
27
+ #
28
+ # The RightAws::EmrInterface class provides a complete interface to Amazon
29
+ # Elastic Map Reduce service.
30
+ #
31
+ # For explanations of the semantics of each call, please refer to Amazon's
32
+ # documentation at
33
+ # http://aws.amazon.com/documentation/elasticmapreduce/
34
+ #
35
+ # Create an interface handle:
36
+ #
37
+ # emr = RightAws::EmrInterface.new(aws_access_key_id, aws_secret_access_key)
38
+ #
39
+ # Create a job flow:
40
+ #
41
+ # emr.run_job_flow(
42
+ # :name => 'job flow 1',
43
+ # :master_instance_type => 'm1.large',
44
+ # :slave_instance_type => 'm1.large',
45
+ # :instance_count => 5,
46
+ # :log_uri => 's3n://bucket/path/to/logs',
47
+ # :steps => [{
48
+ # :name => 'step 1',
49
+ # :jar => 's3n://bucket/path/to/code.jar',
50
+ # :main_class => 'com.foobar.emr.Step1',
51
+ # :args => ['arg', 'arg'],
52
+ # }]) #=> "j-9K18HM82Q0AE7"
53
+ #
54
+ # Describe a job flow:
55
+ #
56
+ # emr.describe_job_flows('j-9K18HM82Q0AE7') #=> {...}
57
+ #
58
+ # Terminate a job flow:
59
+ #
60
+ # emr.terminate_job_flows('j-9K18HM82Q0AE7') #=> true
61
+ #
62
+ class EmrInterface < RightAwsBase
63
+ include RightAwsBaseInterface
64
+
65
+ # Amazon EMR API version being used
66
+ API_VERSION = '2009-03-31'
67
+ DEFAULT_HOST = 'elasticmapreduce.amazonaws.com'
68
+ DEFAULT_PATH = '/'
69
+ DEFAULT_PROTOCOL = 'https'
70
+ DEFAULT_PORT = 443
71
+
72
+ @@bench = AwsBenchmarkingBlock.new
73
+ def self.bench_xml
74
+ @@bench.xml
75
+ end
76
+ def self.bench_service
77
+ @@bench.service
78
+ end
79
+
80
+ # Create a new handle to a EMR service.
81
+ #
82
+ # All handles share the same per process or per thread HTTP connection
83
+ # to EMR. Each handle is for a specific account. The params have
84
+ # the following options:
85
+ #
86
+ # * <tt>:endpoint_url</tt> a fully qualified url to Amazon API endpoint
87
+ # (this overwrites: :server, :port, :service, :protocol). Example:
88
+ # 'https://elasticmapreduce.amazonaws.com'
89
+ # * <tt>:server</tt>: EMR service host, default: DEFAULT_HOST
90
+ # * <tt>:port</tt>: EMR service port, default: DEFAULT_PORT
91
+ # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
92
+ # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
93
+ #
94
+ # emr = RightAws::EmrInterface.new('xxxxxxxxxxxxxxxxxxxxx','xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
95
+ # {:logger => Logger.new('/tmp/x.log')}) #=> #<RightAws::EmrInterface::0xb7b3c30c>
96
+ #
97
+ def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
98
+ init({ :name => 'EMR',
99
+ :default_host => ENV['EMR_URL'] ? URI.parse(ENV['EMR_URL']).host : DEFAULT_HOST,
100
+ :default_port => ENV['EMR_URL'] ? URI.parse(ENV['EMR_URL']).port : DEFAULT_PORT,
101
+ :default_service => ENV['EMR_URL'] ? URI.parse(ENV['EMR_URL']).path : DEFAULT_PATH,
102
+ :default_protocol => ENV['EMR_URL'] ? URI.parse(ENV['EMR_URL']).scheme : DEFAULT_PROTOCOL,
103
+ :default_api_version => ENV['EMR_API_VERSION'] || API_VERSION },
104
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
105
+ aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
106
+ params)
107
+ end
108
+
109
+ def generate_request(action, params={}) #:nodoc:
110
+ generate_request_impl(:get, action, params )
111
+ end
112
+
113
+ # Sends request to Amazon and parses the response
114
+ # Raises AwsError if any banana happened
115
+ def request_info(request, parser) #:nodoc:
116
+ request_info_impl(:emr_connection, @@bench, request, parser)
117
+ end
118
+
119
+ #-----------------------------------------------------------------
120
+ # Job Flows
121
+ #-----------------------------------------------------------------
122
+
123
+ EMR_INSTANCES_KEY_MAPPING = { # :nodoc:
124
+ :additional_info => 'AdditionalInfo',
125
+ :log_uri => 'LogUri',
126
+ :name => 'Name',
127
+ :ami_version => 'AmiVersion',
128
+ # JobFlowInstancesConfig
129
+ :ec2_key_name => 'Instances.Ec2KeyName',
130
+ :hadoop_version => 'Instances.HadoopVersion',
131
+ :instance_count => 'Instances.InstanceCount',
132
+ :keep_job_flow_alive_when_no_steps => 'Instances.KeepJobFlowAliveWhenNoSteps',
133
+ :master_instance_type => 'Instances.MasterInstanceType',
134
+ :slave_instance_type => 'Instances.SlaveInstanceType',
135
+ :termination_protected => 'Instances.TerminationProtected',
136
+ # PlacementType
137
+ :availability_zone => 'Instances.Placement.AvailabilityZone',
138
+ }
139
+
140
+ BOOTSTRAP_ACTION_KEY_MAPPING = { # :nodoc:
141
+ :name => 'Name',
142
+ # ScriptBootstrapActionConfig
143
+ :args => 'ScriptBootstrapAction.Args',
144
+ :path => 'ScriptBootstrapAction.Path',
145
+ }
146
+
147
+ INSTANCE_GROUP_KEY_MAPPING = { # :nodoc:
148
+ :bid_price => 'BidPrice',
149
+ :instance_count => 'InstanceCount',
150
+ :instance_role => 'InstanceRole',
151
+ :instance_type => 'InstanceType',
152
+ :market => 'Market',
153
+ :name => 'Name',
154
+ }
155
+
156
+ STEP_CONFIG_KEY_MAPPING = { # :nodoc:
157
+ :action_on_failure => 'ActionOnFailure',
158
+ :name => 'Name',
159
+ # HadoopJarStepConfig
160
+ :args => 'HadoopJarStep.Args',
161
+ :jar => 'HadoopJarStep.Jar',
162
+ :main_class => 'HadoopJarStep.MainClass',
163
+ :properties => 'HadoopJarStep.Properties',
164
+ }
165
+
166
+ KEY_VALUE_KEY_MAPPINGS = {
167
+ :key => 'Key',
168
+ :value => 'Value',
169
+ }
170
+
171
+ # Creates and starts running a new job flow.
172
+ #
173
+ # The job flow will run the steps specified and terminate (unless
174
+ # keep alive option is set).
175
+ #
176
+ # A maximum of 256 steps are allowed in a job flow.
177
+ #
178
+ # At least the name, instance types, instance count and one step
179
+ # must be specified.
180
+ #
181
+ # # simple usage:
182
+ # emr.run_job_flow(
183
+ # :name => 'job flow 1',
184
+ # :master_instance_type => 'm1.large',
185
+ # :slave_instance_type => 'm1.large',
186
+ # :instance_count => 5,
187
+ # :log_uri => 's3n://bucket/path/to/logs',
188
+ # :steps => [{
189
+ # :name => 'step 1',
190
+ # :jar => 's3n://bucket/path/to/code.jar',
191
+ # :main_class => 'com.foobar.emr.Step1',
192
+ # :args => ['arg', 'arg'],
193
+ # }]) #=> "j-9K18HM82Q0AE7"
194
+ #
195
+ # # advanced usage:
196
+ # emr.run_job_flow(
197
+ # :name => 'job flow 1',
198
+ # :ec2_key_name => 'gsg-keypair',
199
+ # :hadoop_version => '0.20',
200
+ # :instance_groups => [{
201
+ # :bid_price => '0.1',
202
+ # :instance_count => '1',
203
+ # :instance_role => 'MASTER',
204
+ # :instance_type => 'm1.small',
205
+ # :market => 'SPOT',
206
+ # :name => 'master group',
207
+ # }, {
208
+ # :bid_price => '0.1',
209
+ # :instance_count => '2',
210
+ # :instance_role => 'CORE',
211
+ # :instance_type => 'm1.small',
212
+ # :market => 'SPOT',
213
+ # :name => 'core group',
214
+ # }, {
215
+ # :bid_price => '0.1',
216
+ # :instance_count => '2',
217
+ # :instance_role => 'TASK',
218
+ # :instance_type => 'm1.small',
219
+ # :market => 'SPOT',
220
+ # :name => 'task group',
221
+ # }],
222
+ # :keep_job_flow_alive_when_no_steps => true,
223
+ # :availability_zone => 'us-east-1a',
224
+ # :termination_protected => true,
225
+ # :log_uri => 's3n://bucket/path/to/logs',
226
+ # :steps => [{
227
+ # :name => 'step 1',
228
+ # :jar => 's3n://bucket/path/to/code.jar',
229
+ # :main_class => 'com.foobar.emr.Step1',
230
+ # :args => ['arg', 'arg'],
231
+ # :properties => {
232
+ # 'property' => 'value',
233
+ # },
234
+ # :action_on_failure => 'TERMINATE_JOB_FLOW',
235
+ # }],
236
+ # :additional_info => '',
237
+ # :bootstrap_actions => [{
238
+ # :name => 'bootstrap action 1',
239
+ # :path => 's3n://bucket/path/to/bootstrap',
240
+ # :args => ['hello', 'world'],
241
+ # }],
242
+ # ) #=> "j-9K18HM82Q0AE7"
243
+ #
244
+ def run_job_flow(options={})
245
+ request_hash = amazonize_run_job_flow(options)
246
+ request_hash.update(amazonize_bootstrap_actions(options[:bootstrap_actions]))
247
+ request_hash.update(amazonize_instance_groups(options[:instance_groups]))
248
+ request_hash.update(amazonize_steps(options[:steps]))
249
+ link = generate_request("RunJobFlow", request_hash)
250
+ request_info(link, RunJobFlowParser.new(:logger => @logger))
251
+ rescue
252
+ on_exception
253
+ end
254
+
255
+ # Returns a list of job flows that match all of supplied parameters.
256
+ #
257
+ # Without parameters, returns job flows started in the last two weeks
258
+ # or running job flows started in the last two months.
259
+ #
260
+ # Regardless of parameters, only jobs started in the last two months
261
+ # are returned.
262
+ #
263
+ # # default list:
264
+ # emr.describe_job_flows #=> [
265
+ # {:keep_job_flow_alive_when_no_steps=>false,
266
+ # :log_uri=>"s3n://bucket/path/to/logs",
267
+ # :master_instance_type=>"m1.small",
268
+ # :availability_zone=>"us-east-1d",
269
+ # :last_state_change_reason=>"Steps completed",
270
+ # :termination_protected=>false,
271
+ # :master_instance_id=>"i-1fe51278",
272
+ # :instance_count=>1,
273
+ # :ready_date_time=>"2011-08-31T18:58:58Z",
274
+ # :bootstrap_actions=>[],
275
+ # :master_public_dns_name=>"ec2-184-78-29-127.compute-1.amazonaws.com",
276
+ # :instance_groups=>
277
+ # [{:instance_request_count=>1,
278
+ # :last_state_change_reason=>"Job flow terminated",
279
+ # :instance_role=>"MASTER",
280
+ # :ready_date_time=>"2011-08-31T18:58:56Z",
281
+ # :instance_running_count=>0,
282
+ # :start_date_time=>"2011-08-31T18:58:19Z",
283
+ # :market=>"ON_DEMAND",
284
+ # :creation_date_time=>"2011-08-31T18:55:36Z",
285
+ # :name=>"master",
286
+ # :instance_group_id=>"ig-1D91GQR7A9H2K",
287
+ # :state=>"ENDED",
288
+ # :instance_type=>"m1.small",
289
+ # :end_date_time=>"2011-08-31T19:01:09Z"}],
290
+ # :start_date_time=>"2011-08-31T18:58:58Z",
291
+ # :steps=>
292
+ # [{:jar=>"s3n://bucket/path/to/code.jar",
293
+ # :main_class=>"com.foobar.emr.Step1",
294
+ # :start_date_time=>"2011-08-31T18:58:58Z",
295
+ # :properties=>{},
296
+ # :args=>[],
297
+ # :creation_date_time=>"2011-08-31T18:55:36Z",
298
+ # :action_on_failure=>"TERMINATE_JOB_FLOW",
299
+ # :name=>"step 1",
300
+ # :state=>"COMPLETED",
301
+ # :end_date_time=>"2011-08-31T19:00:34Z"}],
302
+ # :normalized_instance_hours=>1,
303
+ # :ami_version=>"1.0",
304
+ # :creation_date_time=>"2011-08-31T18:55:36Z",
305
+ # :name=>"jobflow 1",
306
+ # :hadoop_version=>"0.18",
307
+ # :job_flow_id=>"j-9K18HM82Q0AE7",
308
+ # :state=>"COMPLETED",
309
+ # :end_date_time=>"2011-08-31T19:01:09Z"}]
310
+ #
311
+ # # describe specific job flows:
312
+ # emr.describe_job_flows('j-9K18HM82Q0AE7', 'j-2QE0KHA1LP4GS') #=> [...]
313
+ #
314
+ # # specify parameters:
315
+ # emr.describe_job_flows(
316
+ # :created_after => Time.now - 86400,
317
+ # :created_before => Time.now - 3600,
318
+ # :job_flow_ids => ['j-9K18HM82Q0AE7', 'j-2QE0KHA1LP4GS'],
319
+ # :job_flow_states => ['RUNNING']
320
+ # ) #=> [...]
321
+ #
322
+ # # combined job flow list and parameters syntax:
323
+ # emr.describe_job_flows('j-9K18HM82Q0AE7', 'j-2QE0KHA1LP4GS',
324
+ # :job_flow_states => ['RUNNING']
325
+ # ) #=> [...]
326
+ #
327
+ def describe_job_flows(*job_flow_ids_and_options)
328
+ job_flow_ids, options = AwsUtils::split_items_and_params(job_flow_ids_and_options)
329
+ # merge job flow ids passed in as arguments and in options
330
+ unless job_flow_ids.empty?
331
+ # do not modify passed in options
332
+ options = options.dup
333
+ if job_flow_ids_in_options = options[:job_flow_ids]
334
+ # allow the same ids to be passed in either location;
335
+ # remove duplicates
336
+ options[:job_flow_ids] = (job_flow_ids_in_options + job_flow_ids).uniq
337
+ else
338
+ options[:job_flow_ids] = job_flow_ids
339
+ end
340
+ end
341
+ request_hash = {}
342
+ unless (job_flow_ids = options[:job_flow_ids]).right_blank?
343
+ request_hash.update(amazonize_list("JobFlowIds.member", job_flow_ids))
344
+ end
345
+ unless (job_flow_states = options[:job_flow_states]).right_blank?
346
+ request_hash = amazonize_list("JobFlowStates.member", job_flow_states)
347
+ end
348
+ request_hash['CreatedAfter'] = AwsUtils::utc_iso8601(options[:created_after]) unless options[:created_after].right_blank?
349
+ request_hash['CreatedBefore'] = AwsUtils::utc_iso8601(options[:created_before]) unless options[:created_before].right_blank?
350
+ link = generate_request("DescribeJobFlows", request_hash)
351
+ request_cache_or_info(:describe_job_flows, link, DescribeJobFlowsParser, @@bench, nil)
352
+ rescue
353
+ on_exception
354
+ end
355
+
356
+ # Terminates specified job flows.
357
+ #
358
+ # emr.terminate_job_flows('j-9K18HM82Q0AE7') #=> true
359
+ #
360
+ def terminate_job_flows(*job_flow_ids)
361
+ link = generate_request("TerminateJobFlows", amazonize_list('JobFlowIds.member', job_flow_ids))
362
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
363
+ rescue
364
+ on_exception
365
+ end
366
+
367
+ # Locks a job flow so the EC2 instances in the cluster cannot be
368
+ # terminated by user intervention, an API call, or in the event of a
369
+ # job flow error. Cluster will still terminate upon successful completion
370
+ # of the job flow.
371
+ #
372
+ # emr.set_termination_protection(
373
+ # 'j-9K18HM82Q0AE7', 'j-2QE0KHA1LP4GS', :termination_protected => true
374
+ # ) #=> true
375
+ #
376
+ # Protection can be enabled using the shortcut syntax:
377
+ #
378
+ # emr.set_termination_protection('j-9K18HM82Q0AE7') #=> true
379
+ #
380
+ def set_termination_protection(*job_flow_ids_and_options)
381
+ job_flow_ids, options = AwsUtils::split_items_and_params(job_flow_ids_and_options)
382
+ request_hash = amazonize_list('JobFlowIds.member', job_flow_ids)
383
+ request_hash['TerminationProtected'] = case value = options[:termination_protected]
384
+ when true
385
+ 'true'
386
+ when false
387
+ 'false'
388
+ when nil
389
+ # if :termination_protected => nil was given, then unprotect;
390
+ # if no :termination_protected option was given, protect
391
+ if options.has_key?(:termination_protected)
392
+ 'false'
393
+ else
394
+ 'true'
395
+ end
396
+ else
397
+ # pass value through
398
+ value
399
+ end
400
+ link = generate_request("SetTerminationProtection", request_hash)
401
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
402
+ rescue
403
+ on_exception
404
+ end
405
+
406
+ #-----------------------------------------------------------------
407
+ # Steps
408
+ #-----------------------------------------------------------------
409
+
410
+ # Adds steps to a running job flow.
411
+ #
412
+ # A maximum of 256 steps are allowed in a job flow. Steps can only be
413
+ # added to job flows that are starting, bootstrapping, running or waiting.
414
+ #
415
+ # Step configuration options are the same as the ones accepted by
416
+ # run_job_flow.
417
+ #
418
+ # emr.add_job_flow_steps('j-2QE0KHA1LP4GS', {
419
+ # :name => 'step 1',
420
+ # :jar => 's3n://bucket/path/to/code.jar',
421
+ # :main_class => 'com.foobar.emr.Step1',
422
+ # :args => ['arg', 'arg'],
423
+ # :properties => {
424
+ # 'property' => 'value',
425
+ # },
426
+ # :action_on_failure => 'TERMINATE_JOB_FLOW',
427
+ # }) #=> true
428
+ #
429
+ def add_job_flow_steps(job_flow_id, *steps)
430
+ request_hash = amazonize_steps(steps)
431
+ request_hash['JobFlowId'] = job_flow_id
432
+ link = generate_request("AddJobFlowSteps", request_hash)
433
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
434
+ rescue
435
+ on_exception
436
+ end
437
+
438
+ #-----------------------------------------------------------------
439
+ # Instance Groups
440
+ #-----------------------------------------------------------------
441
+
442
+ # Adds instance groups to a running job flow.
443
+ #
444
+ # Instance group configuration options are the same as the ones accepted
445
+ # by run_job_flow.
446
+ #
447
+ # Only task instance groups may be added at runtime.
448
+ # Instance groups cannot be added to job flows that have only a master
449
+ # instance (i.e. 1 instance in total).
450
+ #
451
+ # emr.add_instance_groups('j-2QE0KHA1LP4GS', {
452
+ # :bid_price => '0.1',
453
+ # :instance_count => '2',
454
+ # :instance_role => 'TASK',
455
+ # :instance_type => 'm1.small',
456
+ # :market => 'SPOT',
457
+ # :name => 'core group',
458
+ # }) #=> true
459
+ #
460
+ def add_instance_groups(job_flow_id, *instance_groups)
461
+ request_hash = amazonize_instance_groups(instance_groups, 'InstanceGroups')
462
+ request_hash['JobFlowId'] = job_flow_id
463
+ link = generate_request("AddInstanceGroups", request_hash)
464
+ request_info(link, AddInstanceGroupsParser.new(:logger => @logger))
465
+ rescue
466
+ on_exception
467
+ end
468
+
469
+ MODIFY_INSTANCE_GROUP_KEY_MAPPINGS = {
470
+ :instance_group_id => 'InstanceGroupId',
471
+ :instance_count => 'InstanceCount',
472
+ }
473
+
474
+ # Modifies instance groups.
475
+ #
476
+ # The only modifiable parameter is instance count.
477
+ #
478
+ # An instance group may only be modified when the job flow is running
479
+ # or waiting. Additionally, hadoop 0.20 is required to resize job flows.
480
+ #
481
+ # # general syntax
482
+ # emr.modify_instance_groups(
483
+ # {:instance_group_id => 'ig-P2OPM2L9ZQ4P', :instance_count => 5},
484
+ # {:instance_group_id => 'ig-J82ML0M94A7E', :instance_count => 1}
485
+ # ) #=> true
486
+ #
487
+ # # shortcut syntax
488
+ # emr.modify_instance_groups('ig-P2OPM2L9ZQ4P', 5) #=> true
489
+ #
490
+ # Shortcut syntax supports modifying only one instance group at a time.
491
+ #
492
+ def modify_instance_groups(*args)
493
+ unless args.first.is_a?(Hash)
494
+ if args.length != 2
495
+ raise ArgumentError, "Must be given two arguments if arguments are not hashes"
496
+ end
497
+ args = [{:instance_group_id => args.first, :instance_count => args.last}]
498
+ end
499
+ request_hash = amazonize_list_with_key_mapping('InstanceGroups.member', MODIFY_INSTANCE_GROUP_KEY_MAPPINGS, args)
500
+ link = generate_request("ModifyInstanceGroups", request_hash)
501
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
502
+ rescue
503
+ on_exception
504
+ end
505
+
506
+ private
507
+
508
+ def amazonize_run_job_flow(options) # :nodoc:
509
+ result = {}
510
+ unless options.right_blank?
511
+ EMR_INSTANCES_KEY_MAPPING.each do |local_name, remote_name|
512
+ value = options[local_name]
513
+ result[remote_name] = value unless value.nil?
514
+ end
515
+ end
516
+ result
517
+ end
518
+
519
+ def amazonize_bootstrap_actions(bootstrap_actions, key = 'BootstrapActions.member') # :nodoc:
520
+ result = {}
521
+ unless bootstrap_actions.right_blank?
522
+ bootstrap_actions.each_with_index do |item, index|
523
+ BOOTSTRAP_ACTION_KEY_MAPPING.each do |local_name, remote_name|
524
+ value = item[local_name]
525
+ case local_name
526
+ when :args
527
+ result.update(amazonize_list("#{key}.#{index+1}.#{remote_name}.member", value))
528
+ else
529
+ next if value.nil?
530
+ result["#{key}.#{index+1}.#{remote_name}"] = value
531
+ end
532
+ end
533
+ end
534
+ end
535
+ result
536
+ end
537
+
538
+ def amazonize_instance_groups(instance_groups, key = 'Instances.InstanceGroups') # :nodoc:
539
+ result = {}
540
+ unless instance_groups.right_blank?
541
+ instance_groups.each_with_index do |item, index|
542
+ INSTANCE_GROUP_KEY_MAPPING.each do |local_name, remote_name|
543
+ value = item[local_name]
544
+ case local_name
545
+ when :instance_groups
546
+ result.update(amazonize_list_with_key_mapping("#{key}.member.#{index+1}.#{remote_name}", INSTANCE_GROUP_KEY_MAPPING, value))
547
+ else
548
+ next if value.nil?
549
+ result["#{key}.member.#{index+1}.#{remote_name}"] = value
550
+ end
551
+ end
552
+ end
553
+ end
554
+ result
555
+ end
556
+
557
+ def amazonize_steps(steps, key = 'Steps.member') # :nodoc:
558
+ result = {}
559
+ unless steps.right_blank?
560
+ steps.each_with_index do |item, index|
561
+ STEP_CONFIG_KEY_MAPPING.each do |local_name, remote_name|
562
+ value = item[local_name]
563
+ case local_name
564
+ when :args
565
+ result.update(amazonize_list("#{key}.#{index+1}.#{remote_name}.member", value))
566
+ when :properties
567
+ next if value.right_blank?
568
+ list = value.inject([]) do |l, (k, v)|
569
+ l << {:key => k, :value => v}
570
+ end
571
+ result.update(amazonize_list_with_key_mapping("#{key}.#{index+1}.#{remote_name}.member", KEY_VALUE_KEY_MAPPINGS, list))
572
+ else
573
+ next if value.nil?
574
+ result["#{key}.#{index+1}.#{remote_name}"] = value
575
+ end
576
+ end
577
+ end
578
+ end
579
+ result
580
+ end
581
+
582
+ #-----------------------------------------------------------------
583
+ # PARSERS: Run Job Flow
584
+ #-----------------------------------------------------------------
585
+
586
+ class RunJobFlowParser < RightAWSParser #:nodoc:
587
+ def tagend(name)
588
+ case name
589
+ when 'JobFlowId' then @result = @text
590
+ end
591
+ end
592
+ def reset
593
+ @result = nil
594
+ end
595
+ end
596
+
597
+ #-----------------------------------------------------------------
598
+ # PARSERS: Describe Job Flows
599
+ #-----------------------------------------------------------------
600
+
601
+ class DescribeJobFlowsParser < RightAWSParser #:nodoc:
602
+ def tagstart(name, attributes)
603
+ case full_tag_name
604
+ when %r{/JobFlows/member$}
605
+ @item = { :instance_groups => [],
606
+ :steps => [],
607
+ :bootstrap_actions => [] }
608
+ when %r{/BootstrapActionConfig$}
609
+ @bootstrap_action = {}
610
+ when %r{/InstanceGroups/member$}
611
+ @instance_group = {}
612
+ when %r{/Steps/member$}
613
+ @step = { :args => [],
614
+ :properties => {} }
615
+ end
616
+ end
617
+ def tagend(name)
618
+ case full_tag_name
619
+ when %r{/BootstrapActionConfig} # no trailing $
620
+ case name
621
+ when 'Name'
622
+ @bootstrap_action[:name] = @text
623
+ when 'ScriptBootstrapAction'
624
+ @bootstrap_action[:script_bootstrap_action] = @text
625
+ when 'BootstrapActionConfig'
626
+ @step[:bootstrap_actions] << @bootstrap_action
627
+ end
628
+ when %r{/InstanceGroups/member} # no trailing $
629
+ case name
630
+ when 'BidPrice' then @instance_group[:bid_price] = @text
631
+ when 'CreationDateTime' then @instance_group[:creation_date_time] = @text
632
+ when 'EndDateTime' then @instance_group[:end_date_time] = @text
633
+ when 'InstanceGroupId' then @instance_group[:instance_group_id] = @text
634
+ when 'InstanceRequestCount' then @instance_group[:instance_request_count] = @text.to_i
635
+ when 'InstanceRole' then @instance_group[:instance_role] = @text
636
+ when 'InstanceRunningCount' then @instance_group[:instance_running_count] = @text.to_i
637
+ when 'InstanceType' then @instance_group[:instance_type] = @text
638
+ when 'LastStateChangeReason' then @instance_group[:last_state_change_reason] = @text
639
+ when 'Market' then @instance_group[:market] = @text
640
+ when 'Name' then @instance_group[:name] = @text
641
+ when 'ReadyDateTime' then @instance_group[:ready_date_time] = @text
642
+ when 'StartDateTime' then @instance_group[:start_date_time] = @text
643
+ when 'State' then @instance_group[:state] = @text
644
+ when 'member' then @item[:instance_groups] << @instance_group
645
+ end
646
+ when %r{/Steps/member/StepConfig/HadoopJarStep/Args/member}
647
+ @step[:args] << @text
648
+ when %r{/Steps/member/StepConfig/HadoopJarStep/Properties$}
649
+ @step[:properties][@key] = @value
650
+ when %r{/Steps/member/StepConfig/HadoopJarStep/Properties}
651
+ case name
652
+ when 'Key'
653
+ @key = @text
654
+ when 'Value'
655
+ @value = @text
656
+ end
657
+ when %r{/Steps/member$}
658
+ @item[:steps] << @step
659
+ when %r{/Steps/member} # no trailing $
660
+ case name
661
+ # ExecutionStatusDetail
662
+ when 'CreationDateTime' then @step[:creation_date_time] = @text
663
+ when 'EndDateTime' then @step[:end_date_time] = @text
664
+ when 'LastStateChangeReason' then @step[:last_state_change_reason] = @text
665
+ when 'StartDateTime' then @step[:start_date_time] = @text
666
+ when 'State' then @step[:state] = @text
667
+ # StepConfig
668
+ when 'ActionOnFailure' then @step[:action_on_failure] = @text
669
+ when 'Name' then @step[:name] = @text
670
+ # HadoopJarStepConfig
671
+ when 'Jar' then @step[:jar] = @text
672
+ when 'MainClass' then @step[:main_class] = @text
673
+ end
674
+ when %r{/JobFlows/member$}
675
+ @result << @item
676
+ else
677
+ case name
678
+ when 'AmiVersion' then @item[:ami_version] = @text
679
+ when 'JobFlowId' then @item[:job_flow_id] = @text
680
+ when 'LogUri' then @item[:log_uri] = @text
681
+ when 'Name' then @item[:name] = @text
682
+
683
+ # JobFlowExecutionStatusDetail
684
+ when 'CreationDateTime' then @item[:creation_date_time] = @text
685
+ when 'EndDateTime' then @item[:end_date_time] = @text
686
+ when 'LastStateChangeReason' then @item[:last_state_change_reason] = @text
687
+ when 'ReadyDateTime' then @item[:ready_date_time] = @text
688
+ when 'StartDateTime' then @item[:start_date_time] = @text
689
+ when 'State' then @item[:state] = @text
690
+
691
+ # JobFlowInstancesDetail
692
+ when 'Ec2KeyName' then @item[:ec2_key_name] = @text
693
+ when 'HadoopVersion' then @item[:hadoop_version] = @text
694
+ when 'InstanceCount' then @item[:instance_count] = @text.to_i
695
+ when 'KeepJobFlowAliveWhenNoSteps' then @item[:keep_job_flow_alive_when_no_steps] = case @text when 'true' then true when 'false' then false else @text end
696
+ when 'MasterInstanceId' then @item[:master_instance_id] = @text
697
+ when 'MasterInstanceType' then @item[:master_instance_type] = @text
698
+ when 'MasterPublicDnsName' then @item[:master_public_dns_name] = @text
699
+ when 'NormalizedInstanceHours' then @item[:normalized_instance_hours] = @text.to_i
700
+ # Placement
701
+ when 'AvailabilityZone' then @item[:availability_zone] = @text
702
+ when 'SlaveInstanceType' then @item[:slave_instance_type] = @text
703
+ when 'TerminationProtected' then @item[:termination_protected] = case @text when 'true' then true when 'false' then false else @text end
704
+ end
705
+ end
706
+ end
707
+ def reset
708
+ @result = []
709
+ end
710
+ end
711
+
712
+ #-----------------------------------------------------------------
713
+ # PARSERS: Add Instance Groups
714
+ #-----------------------------------------------------------------
715
+
716
+ class AddInstanceGroupsParser < RightAWSParser #:nodoc:
717
+ def tagend(name)
718
+ case name
719
+ when 'InstanceGroupIds' then @result << @text.strip
720
+ end
721
+ end
722
+ def reset
723
+ @result = []
724
+ end
725
+ end
726
+ end
727
+
728
+ end