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 +87 -18
- data/lib/elasticity/emr.rb +109 -36
- data/lib/elasticity/version.rb +1 -1
- data/spec/fixtures/vcr_cassettes/run_jobflow/word_count.yml +35 -0
- data/spec/fixtures/vcr_cassettes/set_termination_protection/nonexistent_job_flows.yml +35 -0
- data/spec/fixtures/vcr_cassettes/set_termination_protection/protect_multiple_job_flows.yml +32 -0
- data/spec/lib/elasticity/emr_spec.rb +362 -67
- data/spec/spec_helper.rb +22 -0
- metadata +10 -4
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
|
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
|
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
|
-
|
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
|
-
:
|
36
|
-
:
|
37
|
-
:
|
36
|
+
:instance_count => 1,
|
37
|
+
:instance_role => "TASK",
|
38
|
+
:instance_type => "m1.small",
|
38
39
|
:market => "ON_DEMAND",
|
39
|
-
:name
|
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["
|
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["
|
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["
|
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["
|
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
|
-
'
|
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["
|
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["
|
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["
|
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["
|
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]
|
data/lib/elasticity/emr.rb
CHANGED
@@ -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(
|
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
|
-
# :
|
26
|
-
# :
|
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
|
-
|
40
|
-
|
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 = {
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
#
|
107
|
-
#
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
data/lib/elasticity/version.rb
CHANGED
@@ -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(
|
15
|
+
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
13
16
|
instance_group_config = {
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
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(
|
31
|
+
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
29
32
|
instance_group_config = {
|
30
33
|
:bid_price => 0,
|
31
|
-
:
|
32
|
-
:
|
33
|
-
:
|
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
|
-
{:
|
66
|
-
|
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(
|
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(
|
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 "#
|
481
|
+
describe "#set_termination_protection" do
|
248
482
|
|
249
483
|
describe "integration happy path" do
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
emr.
|
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
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
-
|
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
|
-
@
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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(
|
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:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: "0.
|
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-
|
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
|