right_aws 1.9.0 → 3.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 (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