elasticity 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.mediawiki CHANGED
@@ -1,8 +1,8 @@
1
1
  Elasticity provides programmatic access to Amazon's Elastic Map Reduce service. The aim is to conveniently wrap the API operations in a manner that makes working with EMR job flows from Ruby more enjoyable. At the very least, using Elasticity allows you to easily experiment with the EMR API :)
2
2
 
3
- '''BACKLOG''': Have a look at the backlog [https://www.pivotaltracker.com/projects/272429 here] (Pivotal Tracker).
3
+ '''BACKLOG''': Have a look at the [https://www.pivotaltracker.com/projects/272429 backlog] to see where this is headed.
4
4
 
5
- '''CREDITS''': AWS signing was used from [http://www.rightscale.com/ RightScale's] amazing [https://github.com/rightscale/right_aws right_aws gem] which works extraordinarily well! If you need access to any AWS service (EC2, S3, etc.), have a look.
5
+ '''CREDITS''': AWS signing was used from [http://www.rightscale.com/ RightScale's] amazing [https://github.com/rightscale/right_aws right_aws gem] which works extraordinarily well! If you need access to any AWS service (EC2, S3, etc.), have a look. Used camelize from ActiveSupport as well, thank you \Rails :)
6
6
 
7
7
  = Installation and Usage =
8
8
 
@@ -14,9 +14,9 @@ All you have to do is <code>require 'elasticity'</code> and you're all set!
14
14
 
15
15
  = API Reference =
16
16
 
17
- Elasticity wraps (well, eventually!) all of the EMR API calls. Please see the Amazon guide for details on these operations because the default values aren't obvious (e.g. the meaning of <code>DescribeJobFlows</code> without parameters).
17
+ Elasticity wraps all of the EMR API calls. Please see the Amazon guide for details on these operations because the default values aren't obvious (e.g. the meaning of <code>DescribeJobFlows</code> without parameters).
18
18
 
19
- Alternatively, you may opt for "direct" access to the API where you specify the params and Elasticity takes care of the signing for you, responding with the XML from Amazon. Direct access is described below the API catalog.
19
+ You may opt for "direct" access to the API where you specify the params and Elasticity takes care of the signing for you, responding with the XML from Amazon. Direct access is described below the API catalog.
20
20
 
21
21
  In addition to the [http://aws.amazon.com/elasticmapreduce/ AWS EMR subsite], there are three primary resources of reference information for EMR:
22
22
 
@@ -31,12 +31,13 @@ Unfortunately, the documentation is sometimes incorrect and sometimes missing.
31
31
  AddInstanceGroups adds a group of instances to an existing job flow. The available instance configuration options are listed in the EMR API reference. They've been converted to be more Ruby-like in the wrappers, as shown in the example below.
32
32
 
33
33
  <pre>
34
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
34
35
  instance_group_config = {
35
- :count => 1,
36
- :role => "TASK",
37
- :type => "m1.small",
36
+ :instance_count => 1,
37
+ :instance_role => "TASK",
38
+ :instance_type => "m1.small",
38
39
  :market => "ON_DEMAND",
39
- :name => "Go Canucks!"
40
+ :name => "Go Canucks Go!"
40
41
  }
41
42
  emr.add_instance_groups("j-26LIXPUNSC0M3", [instance_group_config])
42
43
 
@@ -60,7 +61,7 @@ Some combinations of the options will be rejected by Amazon and some once-valid
60
61
  DescribeJobFlows returns detailed information as to the state of all jobs. Currently this is wrapped in an <code>Elasticity::JobFlow</code> that contains the <code>name</code>, <code>jobflow_id</code> and <code>state</code>.
61
62
 
62
63
  <pre>
63
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
64
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
64
65
  jobflows = emr.describe_jobflows
65
66
  p jobflows.map(&:name)
66
67
 
@@ -72,14 +73,14 @@ DescribeJobFlows returns detailed information as to the state of all jobs. Curr
72
73
  A job flow contains several "instance groups" of various types. These instances are where the work for your EMR task occurs. After a job flow has been created, you can find these instance groups in the AWS web UI by clicking on a job flow and then clicking on the "Instance Groups" tab.
73
74
 
74
75
  <pre>
75
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
76
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
76
77
  emr.modify_instance_groups({"ig-2T1HNUO61BG3O" => 3})
77
78
  </pre>
78
79
 
79
80
  If there's an error, you'll receive an ArgumentError containing the message from Amazon. For example if you attempt to modify an instance group that's part of a terminated job flow:
80
81
 
81
82
  <pre>
82
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
83
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
83
84
  emr.modify_instance_groups({"ig-some_terminated_group" => 3})
84
85
 
85
86
  > ArgumentError: An instance group may only be modified when the job flow is running or waiting
@@ -88,7 +89,7 @@ If there's an error, you'll receive an ArgumentError containing the message from
88
89
  Or if you attempt to increase the instance count of the MASTER instance group:
89
90
 
90
91
  <pre>
91
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
92
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
92
93
  emr.modify_instance_groups({"ig-some_terminated_group" => 3})
93
94
 
94
95
  > ArgumentError: A master instance group may not be modified
@@ -96,21 +97,89 @@ Or if you attempt to increase the instance count of the MASTER instance group:
96
97
 
97
98
  == RunJobFlow ==
98
99
 
99
- ''(not yet supported)''
100
+ RunJobFlow creates and starts a new job flow. Specifying the arguments to RunJobFlow is a bit of a hot mess at the moment, requiring you to understand the EMR syntax as well as the data structure for specifying jobs. Here's a beefy example:
101
+
102
+ <pre>
103
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
104
+ jobflow_id = emr.run_job_flow({
105
+ :name => "Elasticity Test Flow (EMR Pig Script)",
106
+ :instances => {
107
+ :ec2_key_name => "sharethrough-dev",
108
+ :hadoop_version => "0.20",
109
+ :instance_count => 2,
110
+ :master_instance_type => "m1.small",
111
+ :placement => {
112
+ :availability_zone => "us-east-1a"
113
+ },
114
+ :slave_instance_type => "m1.small",
115
+ },
116
+ :steps => [
117
+ {
118
+ :action_on_failure => "TERMINATE_JOB_FLOW",
119
+ :hadoop_jar_step => {
120
+ :args => [
121
+ "s3://elasticmapreduce/libs/pig/pig-script",
122
+ "--base-path",
123
+ "s3://elasticmapreduce/libs/pig/",
124
+ "--install-pig"
125
+ ],
126
+ :jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
127
+ },
128
+ :name => "Setup Pig"
129
+ },
130
+ {
131
+ :action_on_failure => "TERMINATE_JOB_FLOW",
132
+ :hadoop_jar_step => {
133
+ :args => [
134
+ "s3://elasticmapreduce/libs/pig/pig-script",
135
+ "--run-pig-script",
136
+ "--args",
137
+ "-p",
138
+ "INPUT=s3n://elasticmapreduce/samples/pig-apache/input",
139
+ "-p",
140
+ "OUTPUT=s3n://slif-elasticity/pig-apache/output/2011-04-19",
141
+ "s3n://elasticmapreduce/samples/pig-apache/do-reports.pig"
142
+ ],
143
+ :jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
144
+ },
145
+ :name => "Run Pig Script"
146
+ }
147
+ ]
148
+ })
149
+
150
+ > "j-129V5AQFMKO1C"
151
+ </pre>
152
+
153
+ Currently Elasticity doesn't do much to ease this pain although this is what I would like to focus on in coming releases. Feel free to ship ideas my way. In the meantime, have a look at the EMR API [http://awsdocs.s3.amazonaws.com/ElasticMapReduce/20090331/emr-api-20090331.pdf PDF] under the RunJobFlow action and riff off of the example here.
154
+
155
+ == SetTerminationProtection ==
156
+
157
+ Enable or disable "termination protection" on the specified job flows. Termination protection prevents a job flow from from being terminated by any user-initiated action.
158
+
159
+ <pre>
160
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
161
+ emr.set_termination_protection(["j-1B4D1XP0C0A35", "j-1YG2MYL0HVYS5"])
162
+ </pre>
163
+
164
+ To disable termination protection, specify false as the second parameter.
165
+
166
+ <pre>
167
+ emr.set_termination_protection(["j-1B4D1XP0C0A35", "j-1YG2MYL0HVYS5"], false)
168
+ </pre>
100
169
 
101
170
  == TerminateJobFlows ==
102
171
 
103
- When the job flow '''exists''', you will receive no output. This is because Amazon does not return anything other than a 200 when you terminate a job flow :) You'll want to continuously poll with DescribeJobFlows to see when the job was actually terminated.
172
+ Terminate the specified job flow. When the job flow '''exists''', you will receive no output. This is because Amazon does not return anything other than a 200 when you terminate a job flow :) You'll want to continuously poll with DescribeJobFlows to see when the job was actually terminated.
104
173
 
105
174
  <pre>
106
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
175
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
107
176
  emr.terminate_jobflows("j-BOWBV7884XD0")
108
177
  </pre>
109
178
 
110
179
  When the job flow '''doesn't exist''':
111
180
 
112
181
  <pre>
113
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
182
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
114
183
  emr.terminate_jobflows("no-flow")
115
184
 
116
185
  > ArgumentError: Job flow 'no-flow' does not exist.
@@ -121,7 +190,7 @@ When the job flow '''doesn't exist''':
121
190
  If you're fine with Elasticity's invocation wrapping and would prefer to get at the resulting XML rather than the wrapped response, throw a block our way and we'll yield the result. This still saves you the trouble of having to create the params and sign the request yet gives you direct access to the response XML for your parsing pleasure.
122
191
 
123
192
  <pre>
124
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
193
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
125
194
  emr.describe_jobflows{|xml| puts xml[0..77]}
126
195
 
127
196
  > <DescribeJobFlowsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/200
@@ -132,7 +201,7 @@ If you're fine with Elasticity's invocation wrapping and would prefer to get at
132
201
  If you're chomping at the bit to initiate some EMR functionality that isn't wrapped (or isn't wrapped in a way you prefer :) feel free to access the AWS EMR API directly by using <code>EMR.direct()</code>. You can find the allowed values in Amazon's EMR API [http://docs.amazonwebservices.com/ElasticMapReduce/latest/DeveloperGuide/index.html developer documentation].
133
202
 
134
203
  <pre>
135
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
204
+ emr = Elasticity::EMR.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_KEY"])
136
205
  params = {"Operation" => "DescribeJobFlows"}
137
206
  result_xml = emr.direct(params)
138
207
  result_xml[0..78]
@@ -8,7 +8,7 @@ module Elasticity
8
8
 
9
9
  # Lists all jobflows in all states.
10
10
  def describe_jobflows
11
- aws_result = @aws_request.aws_emr_request({"Operation" => "DescribeJobFlows"})
11
+ aws_result = @aws_request.aws_emr_request(EMR.convert_ruby_to_aws(:operation => "DescribeJobFlows"))
12
12
  xml_doc = Nokogiri::XML(aws_result)
13
13
  xml_doc.remove_namespaces!
14
14
  yield aws_result if block_given?
@@ -22,11 +22,11 @@ module Elasticity
22
22
  #
23
23
  # instance_group_config = {
24
24
  # :bid_price => 5,
25
- # :count => 1,
26
- # :role => "TASK",
27
- # :type => "m1.small",
25
+ # :instance_count => 1,
26
+ # :instance_role => "TASK",
28
27
  # :market => "SPOT",
29
28
  # :name => "Go Canucks Go!"
29
+ # :type => "m1.small",
30
30
  # }
31
31
  #
32
32
  # add_instance_groups takes an array of {}.
@@ -36,19 +36,12 @@ module Elasticity
36
36
  # ["ig-2GOVEN6HVJZID", "ig-1DU9M2UQMM051", "ig-3DZRW4Y2X4S", ...]
37
37
  def add_instance_groups(jobflow_id, instance_group_configs)
38
38
  params = {
39
- "Operation" => "AddInstanceGroups",
40
- "JobFlowId" => jobflow_id
39
+ :operation => "AddInstanceGroups",
40
+ :job_flow_id => jobflow_id,
41
+ :instance_groups => instance_group_configs
41
42
  }
42
- instance_group_configs.each_with_index do |ig_config, index|
43
- params.merge!("InstanceGroups.member.#{index+1}.BidPrice" => ig_config[:bid_price]) if ig_config[:bid_price]
44
- params.merge!("InstanceGroups.member.#{index+1}.InstanceCount" => ig_config[:count]) if ig_config[:count]
45
- params.merge!("InstanceGroups.member.#{index+1}.InstanceRole" => ig_config[:role]) if ig_config[:role]
46
- params.merge!("InstanceGroups.member.#{index+1}.InstanceType" => ig_config[:type]) if ig_config[:type]
47
- params.merge!("InstanceGroups.member.#{index+1}.Market" => ig_config[:market]) if ig_config[:market]
48
- params.merge!("InstanceGroups.member.#{index+1}.Name" => ig_config[:name]) if ig_config[:name]
49
- end
50
43
  begin
51
- aws_result = @aws_request.aws_emr_request(params)
44
+ aws_result = @aws_request.aws_emr_request(EMR.convert_ruby_to_aws(params))
52
45
  xml_doc = Nokogiri::XML(aws_result)
53
46
  xml_doc.remove_namespaces!
54
47
  instance_group_ids = []
@@ -58,7 +51,7 @@ module Elasticity
58
51
  yield aws_result if block_given?
59
52
  instance_group_ids
60
53
  rescue RestClient::BadRequest => e
61
- raise ArgumentError, parse_error_response(e.http_body)
54
+ raise ArgumentError, EMR.parse_error_response(e.http_body)
62
55
  end
63
56
  end
64
57
 
@@ -71,18 +64,53 @@ module Elasticity
71
64
  #
72
65
  # {"ig-1" => 40, "ig-2" => 5, ...}
73
66
  def modify_instance_groups(instance_group_config)
74
- params = {"Operation" => "ModifyInstanceGroups"}
75
- instance_group_config.keys.each_with_index do |instance_group, index|
76
- params.merge!(
77
- "InstanceGroups.member.#{index+1}.InstanceGroupId" => instance_group,
78
- "InstanceGroups.member.#{index+1}.InstanceCount" => instance_group_config[instance_group]
79
- )
67
+ params = {
68
+ :operation => "ModifyInstanceGroups",
69
+ :instance_groups => instance_group_config.map { |k, v| {:instance_group_id => k, :instance_count => v} }
70
+ }
71
+ begin
72
+ aws_result = @aws_request.aws_emr_request(EMR.convert_ruby_to_aws(params))
73
+ yield aws_result if block_given?
74
+ rescue RestClient::BadRequest => e
75
+ raise ArgumentError, EMR.parse_error_response(e.http_body)
80
76
  end
77
+ end
78
+
79
+ # TODO DOCUMENT ME
80
+ def run_job_flow(job_flow_config)
81
+ params = {
82
+ :operation => "RunJobFlow",
83
+ }.merge!(job_flow_config)
81
84
  begin
82
- aws_result = @aws_request.aws_emr_request(params)
85
+ aws_result = @aws_request.aws_emr_request(EMR.convert_ruby_to_aws(params))
83
86
  yield aws_result if block_given?
87
+ xml_doc = Nokogiri::XML(aws_result)
88
+ xml_doc.remove_namespaces!
89
+ xml_doc.xpath("/RunJobFlowResponse/RunJobFlowResult/JobFlowId").text
84
90
  rescue RestClient::BadRequest => e
85
- raise ArgumentError, parse_error_response(e.http_body)
91
+ raise ArgumentError, EMR.parse_error_response(e.http_body)
92
+ end
93
+ end
94
+
95
+ # Enabled or disable "termination protection" on the specified job flows.
96
+ # Termination protection prevents a job flow from being terminated by a
97
+ # user initiated action, although the job flow will still terminate
98
+ # naturally.
99
+ #
100
+ # Takes an [] of job flow IDs.
101
+ #
102
+ # ["j-1B4D1XP0C0A35", "j-1YG2MYL0HVYS5", ...]
103
+ def set_termination_protection(jobflow_ids, protection_enabled=true)
104
+ params = {
105
+ :operation => "SetTerminationProtection",
106
+ :termination_protected => protection_enabled,
107
+ :job_flow_ids => jobflow_ids
108
+ }
109
+ begin
110
+ aws_result = @aws_request.aws_emr_request(EMR.convert_ruby_to_aws(params))
111
+ yield aws_result if block_given?
112
+ rescue RestClient::BadRequest => e
113
+ raise ArgumentError, EMR.parse_error_response(e.http_body)
86
114
  end
87
115
  end
88
116
 
@@ -91,32 +119,77 @@ module Elasticity
91
119
  # the state of the jobflow. Raises ArgumentError if the specified job
92
120
  # flow does not exist.
93
121
  def terminate_jobflows(jobflow_id)
122
+ params = {
123
+ :operation => "TerminateJobFlows",
124
+ :job_flow_ids => [jobflow_id]
125
+ }
94
126
  begin
95
- aws_result = @aws_request.aws_emr_request({
96
- "Operation" => "TerminateJobFlows",
97
- "JobFlowIds.member.1" => jobflow_id
98
- })
127
+ aws_result = @aws_request.aws_emr_request(EMR.convert_ruby_to_aws(params))
99
128
  yield aws_result if block_given?
100
129
  rescue RestClient::BadRequest
101
130
  raise ArgumentError, "Job flow '#{jobflow_id}' does not exist."
102
131
  end
103
132
  end
104
133
 
105
- # Pass the specified params hash directly through to the AWS request
106
- # URL. Use this if you want to perform an operation that hasn't yet
107
- # been wrapped by Elasticity or you just want to see the response
108
- # XML for yourself :)
134
+ # Pass the specified params hash directly through to the AWS request URL.
135
+ # Use this if you want to perform an operation that hasn't yet been wrapped
136
+ # by Elasticity or you just want to see the response XML for yourself :)
109
137
  def direct(params)
110
138
  @aws_request.aws_emr_request(params)
111
139
  end
112
140
 
113
141
  private
114
142
 
115
- def parse_error_response(error_xml)
116
- xml_doc = Nokogiri::XML(error_xml)
117
- xml_doc.remove_namespaces!
118
- xml_doc.xpath("/ErrorResponse/Error/Message").text
143
+ class << self
144
+
145
+ # AWS error responses all follow the same form. Extract the message from
146
+ # the error document.
147
+ def parse_error_response(error_xml)
148
+ xml_doc = Nokogiri::XML(error_xml)
149
+ xml_doc.remove_namespaces!
150
+ xml_doc.xpath("/ErrorResponse/Error/Message").text
151
+ end
152
+
153
+ # Since we use the same structure as AWS, we can generate AWS param names
154
+ # from the Ruby versions of those names (and the param nesting).
155
+ def convert_ruby_to_aws(params)
156
+ result = {}
157
+ params.each do |key, value|
158
+ case value
159
+ when Array
160
+ prefix = "#{camelize(key.to_s)}.member"
161
+ value.each_with_index do |item, index|
162
+ if item.is_a?(String)
163
+ result["#{prefix}.#{index+1}"] = item
164
+ else
165
+ convert_ruby_to_aws(item).each do |nested_key, nested_value|
166
+ result["#{prefix}.#{index+1}.#{nested_key}"] = nested_value
167
+ end
168
+ end
169
+ end
170
+ when Hash
171
+ prefix = "#{camelize(key.to_s)}"
172
+ convert_ruby_to_aws(value).each do |nested_key, nested_value|
173
+ result["#{prefix}.#{nested_key}"] = nested_value
174
+ end
175
+ else
176
+ result[camelize(key.to_s)] = value
177
+ end
178
+ end
179
+ result
180
+ end
181
+
182
+ # (Used from Rails' ActiveSupport)
183
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
184
+ if first_letter_in_uppercase
185
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
186
+ else
187
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
188
+ end
189
+ end
190
+
119
191
  end
120
192
 
121
193
  end
194
+
122
195
  end
@@ -1,3 +1,3 @@
1
1
  module Elasticity
2
- VERSION = "0.5"
2
+ VERSION = "0.6"
3
3
  end
@@ -0,0 +1,35 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: !ruby/regexp /^http:\/\/elasticmapreduce.amazonaws.com:80\/\?AWSAccessKeyId=AKIAI7HEMMNKGT6VFFSA&Instances\.Ec2KeyName=sharethrough-dev&Instances\.HadoopVersion=0\.20&Instances\.InstanceCount=2&Instances\.MasterInstanceType=m1\.small&Instances\.Placement\.AvailabilityZone=us-east-1a&Instances\.SlaveInstanceType=m1\.small&Name=Elasticity%20Test%20Flow%20\(EMR%20Pig%20Script\)&Operation=RunJobFlow&.*&Steps\.member\.1\.ActionOnFailure=TERMINATE_JOB_FLOW&Steps\.member\.1\.HadoopJarStep\.Args\.member\.1=s3:\/\/elasticmapreduce\/libs\/pig\/pig-script&Steps\.member\.1\.HadoopJarStep\.Args\.member\.2=--base-path&Steps\.member\.1\.HadoopJarStep\.Args\.member\.3=s3:\/\/elasticmapreduce\/libs\/pig\/&Steps\.member\.1\.HadoopJarStep\.Args\.member\.4=--install-pig&Steps\.member\.1\.HadoopJarStep\.Jar=s3:\/\/elasticmapreduce\/libs\/script-runner\/script-runner\.jar&Steps\.member\.1\.Name=Setup%20Pig&Steps\.member\.2\.ActionOnFailure=TERMINATE_JOB_FLOW&Steps\.member\.2\.HadoopJarStep\.Args\.member\.1=s3:\/\/elasticmapreduce\/libs\/pig\/pig-script&Steps\.member\.2\.HadoopJarStep\.Args\.member\.2=--run-pig-script&Steps\.member\.2\.HadoopJarStep\.Args\.member\.3=--args&Steps\.member\.2\.HadoopJarStep\.Args\.member\.4=-p&Steps\.member\.2\.HadoopJarStep\.Args\.member\.5=INPUT=s3n:\/\/elasticmapreduce\/samples\/pig-apache\/input&Steps\.member\.2\.HadoopJarStep\.Args\.member\.6=-p&Steps\.member\.2\.HadoopJarStep\.Args\.member\.7=OUTPUT=s3n:\/\/slif-elasticity\/pig-apache\/output\/2011-04-19&Steps\.member\.2\.HadoopJarStep\.Args\.member\.8=s3n:\/\/elasticmapreduce\/samples\/pig-apache\/do-reports\.pig&Steps\.member\.2\.HadoopJarStep\.Jar=s3:\/\/elasticmapreduce\/libs\/script-runner\/script-runner\.jar&Steps\.member\.2\.Name=Run%20Pig%20Script/
6
+ body:
7
+ headers:
8
+ accept:
9
+ - "*/*; q=0.5, application/xml"
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ response: !ruby/struct:VCR::Response
13
+ status: !ruby/struct:VCR::ResponseStatus
14
+ code: 200
15
+ message: OK
16
+ headers:
17
+ x-amzn-requestid:
18
+ - b22f4aea-6a4b-11e0-9ddc-a168e244afdb
19
+ content-type:
20
+ - text/xml
21
+ date:
22
+ - Tue, 19 Apr 2011 06:10:16 GMT
23
+ content-length:
24
+ - "296"
25
+ body: |
26
+ <RunJobFlowResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
27
+ <RunJobFlowResult>
28
+ <JobFlowId>j-G6N5HA528AD4</JobFlowId>
29
+ </RunJobFlowResult>
30
+ <ResponseMetadata>
31
+ <RequestId>b22f4aea-6a4b-11e0-9ddc-a168e244afdb</RequestId>
32
+ </ResponseMetadata>
33
+ </RunJobFlowResponse>
34
+
35
+ http_version: "1.1"
@@ -0,0 +1,35 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: !ruby/regexp /^http:\/\/elasticmapreduce.amazonaws.com:80\/\?AWSAccessKeyId=AKIAI7HEMMNKGT6VFFSA&JobFlowIds.member.1=j-1B4D1XP0C0A35&JobFlowIds.member.2=j-2&Operation=SetTerminationProtection.*&TerminationProtected=true/
6
+ body:
7
+ headers:
8
+ accept:
9
+ - "*/*; q=0.5, application/xml"
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ response: !ruby/struct:VCR::Response
13
+ status: !ruby/struct:VCR::ResponseStatus
14
+ code: 400
15
+ message: Bad Request
16
+ headers:
17
+ x-amzn-requestid:
18
+ - dc0ce847-6923-11e0-9da4-8d278034dea6
19
+ content-type:
20
+ - text/xml
21
+ date:
22
+ - Sun, 17 Apr 2011 18:52:35 GMT
23
+ content-length:
24
+ - "289"
25
+ body: |
26
+ <ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
27
+ <Error>
28
+ <Type>Sender</Type>
29
+ <Code>ValidationError</Code>
30
+ <Message>Specified job flow ID not valid</Message>
31
+ </Error>
32
+ <RequestId>dc0ce847-6923-11e0-9da4-8d278034dea6</RequestId>
33
+ </ErrorResponse>
34
+
35
+ http_version: "1.1"
@@ -0,0 +1,32 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: !ruby/regexp /^http:\/\/elasticmapreduce.amazonaws.com:80\/\?AWSAccessKeyId=AKIAI7HEMMNKGT6VFFSA&JobFlowIds.member.1=j-1B4D1XP0C0A35&JobFlowIds.member.2=j-1YG2MYL0HVYS5&Operation=SetTerminationProtection&.*TerminationProtected=true/
6
+ body:
7
+ headers:
8
+ accept:
9
+ - "*/*; q=0.5, application/xml"
10
+ accept-encoding:
11
+ - gzip, deflate
12
+ response: !ruby/struct:VCR::Response
13
+ status: !ruby/struct:VCR::ResponseStatus
14
+ code: 200
15
+ message: OK
16
+ headers:
17
+ x-amzn-requestid:
18
+ - 755ebe8a-6923-11e0-a9c2-c126f1bb4493
19
+ content-type:
20
+ - text/xml
21
+ date:
22
+ - Sun, 17 Apr 2011 18:49:43 GMT
23
+ content-length:
24
+ - "239"
25
+ body: |
26
+ <SetTerminationProtectionResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
27
+ <ResponseMetadata>
28
+ <RequestId>755ebe8a-6923-11e0-a9c2-c126f1bb4493</RequestId>
29
+ </ResponseMetadata>
30
+ </SetTerminationProtectionResponse>
31
+
32
+ http_version: "1.1"
@@ -2,6 +2,9 @@ require 'spec_helper'
2
2
 
3
3
  describe Elasticity::EMR do
4
4
 
5
+ AWS_ACCESS_KEY_ID = ENV["AWS_ACCESS_KEY_ID"]
6
+ AWS_SECRET_KEY = ENV["AWS_SECRET_KEY"]
7
+
5
8
  describe "#add_instance_groups" do
6
9
 
7
10
  describe "integration happy path" do
@@ -9,11 +12,11 @@ describe Elasticity::EMR do
9
12
  context "when properly specified" do
10
13
  use_vcr_cassette "add_instance_groups/one_group_successful", :record => :none
11
14
  it "should add the instance groups" do
12
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
15
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
13
16
  instance_group_config = {
14
- :count => 1,
15
- :role => "TASK",
16
- :type => "m1.small",
17
+ :instance_count => 1,
18
+ :instance_role => "TASK",
19
+ :instance_type => "m1.small",
17
20
  :market => "ON_DEMAND",
18
21
  :name => "Go Canucks Go!"
19
22
  }
@@ -25,12 +28,12 @@ describe Elasticity::EMR do
25
28
  context "when improperly specified" do
26
29
  use_vcr_cassette "add_instance_groups/one_group_unsuccessful", :record => :none
27
30
  it "should add the instance groups" do
28
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
31
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
29
32
  instance_group_config = {
30
33
  :bid_price => 0,
31
- :count => 1,
32
- :role => "TASK",
33
- :type => "m1.small",
34
+ :instance_count => 1,
35
+ :instance_role => "TASK",
36
+ :instance_type => "m1.small",
34
37
  :market => "ON_DEMAND",
35
38
  :name => "Go Canucks Go!"
36
39
  }
@@ -62,8 +65,8 @@ describe Elasticity::EMR do
62
65
 
63
66
  it "should iterate over them and send the correct params to AWS" do
64
67
  instance_group_configs = [
65
- {:type=>"m1.small", :role=>"CORE", :market=>"ON_DEMAND", :count=>1, :name=>"Go Canucks Go!", :bid_price=>0},
66
- {:type=>"m1.small", :role=>"CORE", :market=>"ON_DEMAND", :count=>1, :name=>"Go Canucks Go!", :bid_price=>0},
68
+ {:instance_type=>"m1.small", :instance_role=>"CORE", :market=>"ON_DEMAND", :instance_count=>1, :name=>"Go Canucks Go!", :bid_price=>0},
69
+ {:instance_type=>"m1.small", :instance_role=>"CORE", :market=>"ON_DEMAND", :instance_count=>1, :name=>"Go Canucks Go!", :bid_price=>0},
67
70
  ]
68
71
  aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
69
72
  aws_request.should_receive(:aws_emr_request).with({
@@ -119,7 +122,7 @@ describe Elasticity::EMR do
119
122
  describe "integration happy path" do
120
123
  use_vcr_cassette "describe_jobflows/all_jobflows", :record => :none
121
124
  it "should return the names of all running job flows" do
122
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
125
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
123
126
  jobflows = emr.describe_jobflows
124
127
  jobflows.map(&:name).should == ["WM+RS", "Interactive Audience Hive Test", "Audience (Hive)", "Audience Reporting"]
125
128
  jobflows.map(&:jobflow_id).should == ["j-1MZ5TVWFJRSKN", "j-38EU2XZQP9KJ4", "j-2TDCVGEEHOFI9", "j-NKKQ429D858I"]
@@ -179,13 +182,244 @@ describe Elasticity::EMR do
179
182
 
180
183
  end
181
184
 
185
+ describe "#modify_instance_groups" do
186
+
187
+ describe "integration happy path" do
188
+ context "when the instance group exists" do
189
+ use_vcr_cassette "modify_instance_groups/set_instances_to_3", :record => :none
190
+ it "should terminate the specified jobflow" do
191
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
192
+ instance_group_config = {"ig-2T1HNUO61BG3O" => 2}
193
+ emr.modify_instance_groups(instance_group_config)
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "unit tests" do
199
+
200
+ context "when the instance group exists" do
201
+ it "should modify the specified instance group" do
202
+ aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
203
+ aws_request.should_receive(:aws_emr_request).with({
204
+ "Operation" => "ModifyInstanceGroups",
205
+ "InstanceGroups.member.1.InstanceGroupId" => "ig-1",
206
+ "InstanceGroups.member.1.InstanceCount" => 2
207
+ })
208
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
209
+ emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
210
+ emr.modify_instance_groups({"ig-1" => 2})
211
+ end
212
+ end
213
+
214
+ context "when a block is given" do
215
+ it "should yield the XML result" do
216
+ aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
217
+ aws_request.should_receive(:aws_emr_request).and_return("xml result!")
218
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
219
+ emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
220
+ xml_result = nil
221
+ emr.modify_instance_groups({"ig-1" => 2}) do |xml|
222
+ xml_result = xml
223
+ end
224
+ xml_result.should == "xml result!"
225
+ end
226
+ end
227
+
228
+
229
+ context "when there is an error" do
230
+
231
+ before do
232
+ @error_message = "1 validation error detected: Value null at 'instanceGroups.1.member.instanceCount' failed to satisfy constraint: Member must not be null"
233
+ @error_xml = <<-ERROR
234
+ <ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
235
+ <Error>
236
+ <Message>#{@error_message}</Message>
237
+ </Error>
238
+ </ErrorResponse>
239
+ ERROR
240
+ end
241
+
242
+ it "should raise an ArgumentError with the error message" do
243
+ aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
244
+ @exception = RestClient::BadRequest.new
245
+ @exception.should_receive(:http_body).and_return(@error_xml)
246
+ aws_request.should_receive(:aws_emr_request).and_raise(@exception)
247
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
248
+ emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
249
+ lambda {
250
+ emr.modify_instance_groups({"ig-1" => 2})
251
+ }.should raise_error(ArgumentError, @error_message)
252
+ end
253
+
254
+ end
255
+
256
+ end
257
+
258
+ end
259
+
260
+ describe "#run_jobflow" do
261
+
262
+ describe "integration happy path" do
263
+
264
+ context "when the job flow is properly specified" do
265
+ use_vcr_cassette "run_jobflow/word_count", :record => :none
266
+ it "should start the specified job flow" do
267
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
268
+ jobflow_id = emr.run_job_flow({
269
+ :name => "Elasticity Test Flow (EMR Pig Script)",
270
+ :instances => {
271
+ :ec2_key_name => "sharethrough-dev",
272
+ :hadoop_version => "0.20",
273
+ :instance_count => 2,
274
+ :master_instance_type => "m1.small",
275
+ :placement => {
276
+ :availability_zone => "us-east-1a"
277
+ },
278
+ :slave_instance_type => "m1.small",
279
+ },
280
+ :steps => [
281
+ {
282
+ :action_on_failure => "TERMINATE_JOB_FLOW",
283
+ :hadoop_jar_step => {
284
+ :args => [
285
+ "s3://elasticmapreduce/libs/pig/pig-script",
286
+ "--base-path",
287
+ "s3://elasticmapreduce/libs/pig/",
288
+ "--install-pig"
289
+ ],
290
+ :jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
291
+ },
292
+ :name => "Setup Pig"
293
+ },
294
+ {
295
+ :action_on_failure => "TERMINATE_JOB_FLOW",
296
+ :hadoop_jar_step => {
297
+ :args => [
298
+ "s3://elasticmapreduce/libs/pig/pig-script",
299
+ "--run-pig-script",
300
+ "--args",
301
+ "-p",
302
+ "INPUT=s3n://elasticmapreduce/samples/pig-apache/input",
303
+ "-p",
304
+ "OUTPUT=s3n://slif-elasticity/pig-apache/output/2011-04-19",
305
+ "s3n://elasticmapreduce/samples/pig-apache/do-reports.pig"
306
+ ],
307
+ :jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
308
+ },
309
+ :name => "Run Pig Script"
310
+ }
311
+ ]
312
+ })
313
+ jobflow_id.should == "j-G6N5HA528AD4"
314
+ end
315
+ end
316
+ end
317
+
318
+ describe "unit tests" do
319
+ it "should return the job flow ID of the new job" do
320
+ run_jobflow_response = <<-RESPONSE
321
+ <RunJobFlowResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
322
+ <RunJobFlowResult>
323
+ <JobFlowId>j-N500G8Y8U7ZQ</JobFlowId>
324
+ </RunJobFlowResult>
325
+ <ResponseMetadata>
326
+ <RequestId>a6dddf4c-6a49-11e0-b6c0-e9580d1f7304</RequestId>
327
+ </ResponseMetadata>
328
+ </RunJobFlowResponse>
329
+ RESPONSE
330
+ aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
331
+ aws_request.should_receive(:aws_emr_request).and_return(run_jobflow_response)
332
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
333
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
334
+ jobflow_id = emr.run_job_flow({})
335
+ jobflow_id.should == "j-N500G8Y8U7ZQ"
336
+ end
337
+
338
+ it "should run the specified job flow" do
339
+ aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
340
+ aws_request.should_receive(:aws_emr_request).with({
341
+ "Operation" => "RunJobFlow",
342
+ "Name" => "Job flow name",
343
+ "Instances.MasterInstanceType" => "m1.small",
344
+ "Instances.Placement.AvailabilityZone" => "us-east-1a",
345
+ "Steps.member.1.Name" => "Streaming Job",
346
+ "Steps.member.1.ActionOnFailure" => "TERMINATE_JOB_FLOW",
347
+ "Steps.member.1.HadoopJarStep.Jar" => "/home/hadoop/contrib/streaming/hadoop-streaming.jar",
348
+ "Steps.member.1.HadoopJarStep.Args.member.1" => "-input",
349
+ "Steps.member.1.HadoopJarStep.Args.member.2" => "s3n://elasticmapreduce/samples/wordcount/input"
350
+ })
351
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
352
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
353
+ emr.run_job_flow({
354
+ :name => "Job flow name",
355
+ :instances => {
356
+ :master_instance_type => "m1.small",
357
+ :placement => {
358
+ :availability_zone => "us-east-1a"
359
+ }
360
+ },
361
+ :steps => [
362
+ {
363
+ :action_on_failure => "TERMINATE_JOB_FLOW",
364
+ :name => "Streaming Job",
365
+ :hadoop_jar_step => {
366
+ :args => ["-input", "s3n://elasticmapreduce/samples/wordcount/input"],
367
+ :jar => "/home/hadoop/contrib/streaming/hadoop-streaming.jar",
368
+ }
369
+ }
370
+ ]
371
+ })
372
+ end
373
+
374
+ context "when there is an error" do
375
+ before do
376
+ @error_message = "1 validation error detected: Value null at 'instanceGroups.1.member.instanceCount' failed to satisfy constraint: Member must not be null"
377
+ @error_xml = <<-ERROR
378
+ <ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
379
+ <Error>
380
+ <Message>#{@error_message}</Message>
381
+ </Error>
382
+ </ErrorResponse>
383
+ ERROR
384
+ end
385
+
386
+ it "should raise an ArgumentError with the error message" do
387
+ aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
388
+ @exception = RestClient::BadRequest.new
389
+ @exception.should_receive(:http_body).and_return(@error_xml)
390
+ aws_request.should_receive(:aws_emr_request).and_raise(@exception)
391
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
392
+ emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
393
+ lambda {
394
+ emr.run_job_flow({})
395
+ }.should raise_error(ArgumentError, @error_message)
396
+ end
397
+ end
398
+
399
+ context "when a block is given" do
400
+ it "should yield the XML result" do
401
+ aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
402
+ aws_request.should_receive(:aws_emr_request).and_return("jobflow_id!")
403
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
404
+ emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
405
+ xml_result = nil
406
+ emr.run_job_flow({}) do |xml|
407
+ xml_result = xml
408
+ end
409
+ xml_result.should == "jobflow_id!"
410
+ end
411
+ end
412
+ end
413
+
414
+ end
415
+
182
416
  describe "#terminate_jobflows" do
183
417
 
184
418
  describe "integration happy path" do
185
419
  context "when the job flow exists" do
186
420
  use_vcr_cassette "terminate_jobflows/one_jobflow", :record => :none
187
421
  it "should terminate the specified jobflow" do
188
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
422
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
189
423
  emr.terminate_jobflows("j-1MZ5TVWFJRSKN")
190
424
  end
191
425
  end
@@ -244,77 +478,92 @@ describe Elasticity::EMR do
244
478
  end
245
479
  end
246
480
 
247
- describe "#modify_instance_groups" do
481
+ describe "#set_termination_protection" do
248
482
 
249
483
  describe "integration happy path" do
250
- context "when the instance group exists" do
251
- use_vcr_cassette "modify_instance_groups/set_instances_to_3", :record => :none
252
- it "should terminate the specified jobflow" do
253
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
254
- instance_group_config = {"ig-2T1HNUO61BG3O" => 2}
255
- emr.modify_instance_groups(instance_group_config)
484
+
485
+ context "when protecting multiple job flows" do
486
+ use_vcr_cassette "set_termination_protection/protect_multiple_job_flows", :record => :none
487
+ it "should protect the specified job flows" do
488
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
489
+ emr.set_termination_protection(["j-1B4D1XP0C0A35", "j-1YG2MYL0HVYS5"], true)
256
490
  end
257
491
  end
258
- end
259
-
260
- describe "unit tests" do
261
492
 
262
- context "when the instance group exists" do
263
- it "should modify the specified instance group" do
264
- aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
265
- aws_request.should_receive(:aws_emr_request).with({
266
- "Operation" => "ModifyInstanceGroups",
267
- "InstanceGroups.member.1.InstanceGroupId" => "ig-1",
268
- "InstanceGroups.member.1.InstanceCount" => 2
269
- })
270
- Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
271
- emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
272
- emr.modify_instance_groups({"ig-1" => 2})
493
+ context "when specifying a job flow that doesn't exist" do
494
+ use_vcr_cassette "set_termination_protection/nonexistent_job_flows", :record => :none
495
+ it "should have an error" do
496
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
497
+ lambda {
498
+ emr.set_termination_protection(["j-1B4D1XP0C0A35", "j-2"], true)
499
+ }.should raise_error(ArgumentError, "Specified job flow ID not valid")
273
500
  end
274
501
  end
275
502
 
276
- context "when a block is given" do
277
- it "should yield the XML result" do
278
- aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
279
- aws_request.should_receive(:aws_emr_request).and_return("xml result!")
280
- Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
281
- emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
282
- xml_result = nil
283
- emr.modify_instance_groups({"ig-1" => 2}) do |xml|
284
- xml_result = xml
285
- end
286
- xml_result.should == "xml result!"
287
- end
503
+ end
504
+
505
+ describe "unit tests" do
506
+ it "should enable protection on the specified job flows" do
507
+ aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
508
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
509
+ aws_request.should_receive(:aws_emr_request).with({
510
+ "Operation" => "SetTerminationProtection",
511
+ "JobFlowIds.member.1" => "jobflow1",
512
+ "JobFlowIds.member.2" => "jobflow2",
513
+ "TerminationProtected" => true
514
+ })
515
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
516
+ emr.set_termination_protection(["jobflow1", "jobflow2"], true)
288
517
  end
289
518
 
519
+ it "should disable protection on the specified job flows" do
520
+ aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
521
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
522
+ aws_request.should_receive(:aws_emr_request).with({
523
+ "Operation" => "SetTerminationProtection",
524
+ "JobFlowIds.member.1" => "jobflow1",
525
+ "JobFlowIds.member.2" => "jobflow2",
526
+ "TerminationProtected" => false
527
+ })
528
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
529
+ emr.set_termination_protection(["jobflow1", "jobflow2"], false)
530
+ end
290
531
 
291
- context "when there is an error" do
532
+ it "should enable protection when not specified" do
533
+ aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
534
+ Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
535
+ aws_request.should_receive(:aws_emr_request).with({
536
+ "Operation" => "SetTerminationProtection",
537
+ "JobFlowIds.member.1" => "jobflow1",
538
+ "JobFlowIds.member.2" => "jobflow2",
539
+ "TerminationProtected" => true
540
+ })
541
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
542
+ emr.set_termination_protection(["jobflow1", "jobflow2"])
543
+ end
292
544
 
545
+ context "when a block is given" do
293
546
  before do
294
- @error_message = "1 validation error detected: Value null at 'instanceGroups.1.member.instanceCount' failed to satisfy constraint: Member must not be null"
295
- @error_xml = <<-ERROR
296
- <ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
297
- <Error>
298
- <Message>#{@error_message}</Message>
299
- </Error>
300
- </ErrorResponse>
301
- ERROR
547
+ @xml_response = <<-RESPONSE
548
+ <SetTerminationProtectionResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
549
+ <ResponseMetadata>
550
+ <RequestId>755ebe8a-6923-11e0-a9c2-c126f1bb4493</RequestId>
551
+ </ResponseMetadata>
552
+ </SetTerminationProtectionResponse>
553
+ RESPONSE
302
554
  end
303
-
304
- it "should raise an ArgumentError with the error message" do
305
- aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
306
- @exception = RestClient::BadRequest.new
307
- @exception.should_receive(:http_body).and_return(@error_xml)
308
- aws_request.should_receive(:aws_emr_request).and_raise(@exception)
555
+ it "should yield the XML result" do
556
+ aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
309
557
  Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
310
- emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
311
- lambda {
312
- emr.modify_instance_groups({"ig-1" => 2})
313
- }.should raise_error(ArgumentError, @error_message)
558
+ aws_request.should_receive(:aws_emr_request).and_return(@xml_response)
559
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
560
+ xml = nil
561
+ emr.set_termination_protection([]) do |aws_response|
562
+ xml = aws_response
563
+ end
564
+ xml.should == @xml_response
314
565
  end
315
-
316
566
  end
317
-
318
567
  end
319
568
 
320
569
  end
@@ -324,7 +573,7 @@ describe Elasticity::EMR do
324
573
  describe "integration happy path" do
325
574
  use_vcr_cassette "direct/terminate_jobflow", :record => :none
326
575
  it "should terminate the specified jobflow" do
327
- emr = Elasticity::EMR.new(ENV["aws_access_key_id"], ENV["aws_secret_key"])
576
+ emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
328
577
  params = {
329
578
  "Operation" => "TerminateJobFlows",
330
579
  "JobFlowIds.member.1" => "j-1MZ5TVWFJRSKN"
@@ -360,4 +609,50 @@ describe Elasticity::EMR do
360
609
  end
361
610
  end
362
611
 
612
+ describe ".convert_ruby_to_aws" do
613
+ it "should convert the params" do
614
+ add_jobflow_steps_params = {
615
+ :job_flow_id => "j-1",
616
+ :steps => [
617
+ {
618
+ :action_on_failure => "CONTINUE",
619
+ :name => "First New Job Step",
620
+ :hadoop_jar_step => {
621
+ :args => ["arg1", "arg2", "arg3",],
622
+ :jar => "first_step.jar",
623
+ :main_class => "first_class.jar"
624
+ }
625
+ },
626
+ {
627
+ :action_on_failure => "CANCEL_AND_WAIT",
628
+ :name => "Second New Job Step",
629
+ :hadoop_jar_step => {
630
+ :args => ["arg4", "arg5", "arg6",],
631
+ :jar => "second_step.jar",
632
+ :main_class => "second_class.jar"
633
+ }
634
+ }
635
+ ]
636
+ }
637
+ expected_result = {
638
+ "JobFlowId" => "j-1",
639
+ "Steps.member.1.Name" => "First New Job Step",
640
+ "Steps.member.1.ActionOnFailure" => "CONTINUE",
641
+ "Steps.member.1.HadoopJarStep.Jar" => "first_step.jar",
642
+ "Steps.member.1.HadoopJarStep.MainClass" => "first_class.jar",
643
+ "Steps.member.1.HadoopJarStep.Args.member.1" => "arg1",
644
+ "Steps.member.1.HadoopJarStep.Args.member.2" => "arg2",
645
+ "Steps.member.1.HadoopJarStep.Args.member.3" => "arg3",
646
+ "Steps.member.2.Name" => "Second New Job Step",
647
+ "Steps.member.2.ActionOnFailure" => "CANCEL_AND_WAIT",
648
+ "Steps.member.2.HadoopJarStep.Jar" => "second_step.jar",
649
+ "Steps.member.2.HadoopJarStep.MainClass" => "second_class.jar",
650
+ "Steps.member.2.HadoopJarStep.Args.member.1" => "arg4",
651
+ "Steps.member.2.HadoopJarStep.Args.member.2" => "arg5",
652
+ "Steps.member.2.HadoopJarStep.Args.member.3" => "arg6"
653
+ }
654
+ Elasticity::EMR.send(:convert_ruby_to_aws, add_jobflow_steps_params).should == expected_result
655
+ end
656
+ end
657
+
363
658
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,25 @@
1
+ if !ENV["AWS_ACCESS_KEY_ID"] || !ENV["AWS_SECRET_KEY"]
2
+ puts "\n\n\e[32m**********************************************************************************************"
3
+ puts "Please set \e[0;1mAWS_ACCESS_KEY_ID\e[32m and \e[0;1mAWS_SECRET_KEY\e[32m in your environment to run the tests."
4
+ puts ""
5
+ puts "These keys can be found on your AWS Account > Security Credentials page, at the following URL:"
6
+ puts ""
7
+ puts " \e[0;1mhttps://aws-portal.amazon.com/gp/aws/developer/account/index.html?ie=UTF8&action=access-key"
8
+ puts ""
9
+ bourne = !ENV['SHELL'].nil? && ENV['SHELL'] =~ /(bash|ksh|zsh)/
10
+ puts "\e[0mbash, zsh, ksh: #{"\e[35;1mThis is you!\e[0m" if bourne}"
11
+ puts " export AWS_ACCESS_KEY_ID=01234"
12
+ puts " export AWS_SECRET_KEY=56789"
13
+ puts ""
14
+ puts "\e[0mcsh, tcsh: #{"\e[35;1mThis is you!\e[0m" unless bourne}"
15
+ puts " setenv AWS_ACCESS_KEY_ID 01234"
16
+ puts " setenv AWS_SECRET_KEY 56789"
17
+ puts ""
18
+ puts "\e[32m**********************************************************************************************\n\n\n"
19
+ exit
20
+ end
21
+
22
+
1
23
  require 'rubygems'
2
24
  require 'bundler/setup'
3
25
 
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticity
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 7
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 5
9
- version: "0.5"
8
+ - 6
9
+ version: "0.6"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Robert Slifka
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-04-16 00:00:00 -07:00
17
+ date: 2011-04-18 00:00:00 -07:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -133,6 +133,9 @@ files:
133
133
  - spec/fixtures/vcr_cassettes/describe_jobflows/all_jobflows.yml
134
134
  - spec/fixtures/vcr_cassettes/direct/terminate_jobflow.yml
135
135
  - spec/fixtures/vcr_cassettes/modify_instance_groups/set_instances_to_3.yml
136
+ - spec/fixtures/vcr_cassettes/run_jobflow/word_count.yml
137
+ - spec/fixtures/vcr_cassettes/set_termination_protection/nonexistent_job_flows.yml
138
+ - spec/fixtures/vcr_cassettes/set_termination_protection/protect_multiple_job_flows.yml
136
139
  - spec/fixtures/vcr_cassettes/terminate_jobflows/one_jobflow.yml
137
140
  - spec/lib/elasticity/aws_request_spec.rb
138
141
  - spec/lib/elasticity/emr_spec.rb
@@ -178,6 +181,9 @@ test_files:
178
181
  - spec/fixtures/vcr_cassettes/describe_jobflows/all_jobflows.yml
179
182
  - spec/fixtures/vcr_cassettes/direct/terminate_jobflow.yml
180
183
  - spec/fixtures/vcr_cassettes/modify_instance_groups/set_instances_to_3.yml
184
+ - spec/fixtures/vcr_cassettes/run_jobflow/word_count.yml
185
+ - spec/fixtures/vcr_cassettes/set_termination_protection/nonexistent_job_flows.yml
186
+ - spec/fixtures/vcr_cassettes/set_termination_protection/protect_multiple_job_flows.yml
181
187
  - spec/fixtures/vcr_cassettes/terminate_jobflows/one_jobflow.yml
182
188
  - spec/lib/elasticity/aws_request_spec.rb
183
189
  - spec/lib/elasticity/emr_spec.rb