elasticity 1.5 → 2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +2 -1
- data/.rvmrc +1 -1
- data/HISTORY.md +47 -24
- data/LICENSE +1 -1
- data/README.md +165 -317
- data/Rakefile +4 -3
- data/elasticity.gemspec +3 -5
- data/lib/elasticity.rb +10 -5
- data/lib/elasticity/aws_request.rb +81 -20
- data/lib/elasticity/custom_jar_step.rb +33 -0
- data/lib/elasticity/emr.rb +45 -117
- data/lib/elasticity/hadoop_bootstrap_action.rb +27 -0
- data/lib/elasticity/hive_step.rb +57 -0
- data/lib/elasticity/job_flow.rb +109 -39
- data/lib/elasticity/job_flow_status.rb +53 -0
- data/lib/elasticity/job_flow_status_step.rb +35 -0
- data/lib/elasticity/job_flow_step.rb +17 -25
- data/lib/elasticity/pig_step.rb +82 -0
- data/lib/elasticity/support/conditional_raise.rb +23 -0
- data/lib/elasticity/version.rb +1 -1
- data/spec/lib/elasticity/aws_request_spec.rb +159 -51
- data/spec/lib/elasticity/custom_jar_step_spec.rb +59 -0
- data/spec/lib/elasticity/emr_spec.rb +231 -762
- data/spec/lib/elasticity/hadoop_bootstrap_action_spec.rb +26 -0
- data/spec/lib/elasticity/hive_step_spec.rb +74 -0
- data/spec/lib/elasticity/job_flow_integration_spec.rb +197 -0
- data/spec/lib/elasticity/job_flow_spec.rb +369 -138
- data/spec/lib/elasticity/job_flow_status_spec.rb +147 -0
- data/spec/lib/elasticity/job_flow_status_step_spec.rb +73 -0
- data/spec/lib/elasticity/job_flow_step_spec.rb +26 -64
- data/spec/lib/elasticity/pig_step_spec.rb +104 -0
- data/spec/lib/elasticity/support/conditional_raise_spec.rb +35 -0
- data/spec/spec_helper.rb +1 -50
- data/spec/support/be_a_hash_including_matcher.rb +35 -0
- metadata +101 -119
- data/.autotest +0 -2
- data/lib/elasticity/custom_jar_job.rb +0 -38
- data/lib/elasticity/hive_job.rb +0 -69
- data/lib/elasticity/pig_job.rb +0 -109
- data/lib/elasticity/simple_job.rb +0 -51
- data/spec/fixtures/vcr_cassettes/add_instance_groups/one_group_successful.yml +0 -44
- data/spec/fixtures/vcr_cassettes/add_instance_groups/one_group_unsuccessful.yml +0 -41
- data/spec/fixtures/vcr_cassettes/add_jobflow_steps/add_multiple_steps.yml +0 -266
- data/spec/fixtures/vcr_cassettes/custom_jar_job/cloudburst.yml +0 -41
- data/spec/fixtures/vcr_cassettes/describe_jobflows/all_jobflows.yml +0 -75
- data/spec/fixtures/vcr_cassettes/direct/terminate_jobflow.yml +0 -38
- data/spec/fixtures/vcr_cassettes/hive_job/hive_ads.yml +0 -41
- data/spec/fixtures/vcr_cassettes/modify_instance_groups/set_instances_to_3.yml +0 -38
- data/spec/fixtures/vcr_cassettes/pig_job/apache_log_reports.yml +0 -41
- data/spec/fixtures/vcr_cassettes/pig_job/apache_log_reports_with_bootstrap.yml +0 -41
- data/spec/fixtures/vcr_cassettes/run_jobflow/word_count.yml +0 -41
- data/spec/fixtures/vcr_cassettes/set_termination_protection/nonexistent_job_flows.yml +0 -41
- data/spec/fixtures/vcr_cassettes/set_termination_protection/protect_multiple_job_flows.yml +0 -38
- data/spec/fixtures/vcr_cassettes/terminate_jobflows/one_jobflow.yml +0 -38
- data/spec/lib/elasticity/custom_jar_job_spec.rb +0 -118
- data/spec/lib/elasticity/hive_job_spec.rb +0 -90
- data/spec/lib/elasticity/pig_job_spec.rb +0 -226
data/Rakefile
CHANGED
@@ -4,8 +4,9 @@ Bundler::GemHelper.install_tasks
|
|
4
4
|
require 'rake/testtask'
|
5
5
|
require 'rspec/core/rake_task'
|
6
6
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
8
|
+
t.verbose = false
|
9
|
+
end
|
10
|
+
|
7
11
|
desc 'Run specs'
|
8
12
|
task :default => :spec
|
9
|
-
|
10
|
-
desc "Run specs"
|
11
|
-
RSpec::Core::RakeTask.new
|
data/elasticity.gemspec
CHANGED
@@ -8,16 +8,14 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Robert Slifka"]
|
10
10
|
s.homepage = "http://www.github.com/rslifka/elasticity"
|
11
|
-
s.summary = %q{
|
12
|
-
s.description = %q{Programmatic access to Amazon's Elastic Map Reduce service, driven by the Sharethrough team's requirements for belting out EMR jobs.}
|
11
|
+
s.summary = %q{Streamlined, programmatic access to Amazon's Elastic Map Reduce service.}
|
12
|
+
s.description = %q{Streamlined, Programmatic access to Amazon's Elastic Map Reduce service, driven by the Sharethrough team's requirements for belting out EMR jobs.}
|
13
13
|
|
14
14
|
s.add_dependency("rest-client")
|
15
15
|
s.add_dependency("nokogiri")
|
16
16
|
|
17
17
|
s.add_development_dependency("rake")
|
18
|
-
s.add_development_dependency("rspec", "
|
19
|
-
s.add_development_dependency("vcr", "~> 2.0")
|
20
|
-
s.add_development_dependency("webmock", "~> 1.8.0")
|
18
|
+
s.add_development_dependency("rspec", "~> 2.10.0")
|
21
19
|
|
22
20
|
s.files = `git ls-files`.split("\n")
|
23
21
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/elasticity.rb
CHANGED
@@ -4,16 +4,21 @@ require 'time'
|
|
4
4
|
require 'rest_client'
|
5
5
|
require 'nokogiri'
|
6
6
|
|
7
|
+
require 'elasticity/support/conditional_raise'
|
8
|
+
|
7
9
|
require 'elasticity/aws_request'
|
8
10
|
require 'elasticity/emr'
|
9
|
-
|
11
|
+
|
12
|
+
require 'elasticity/hadoop_bootstrap_action'
|
10
13
|
require 'elasticity/job_flow_step'
|
11
14
|
|
12
|
-
require 'elasticity/
|
15
|
+
require 'elasticity/job_flow'
|
16
|
+
require 'elasticity/job_flow_status'
|
17
|
+
require 'elasticity/job_flow_status_step'
|
13
18
|
|
14
|
-
require 'elasticity/
|
15
|
-
require 'elasticity/
|
16
|
-
require 'elasticity/
|
19
|
+
require 'elasticity/custom_jar_step'
|
20
|
+
require 'elasticity/hive_step'
|
21
|
+
require 'elasticity/pig_step'
|
17
22
|
|
18
23
|
module Elasticity
|
19
24
|
end
|
@@ -2,52 +2,113 @@ module Elasticity
|
|
2
2
|
|
3
3
|
class AwsRequest
|
4
4
|
|
5
|
+
attr_reader :access_key
|
6
|
+
attr_reader :secret_key
|
7
|
+
attr_reader :options
|
8
|
+
attr_reader :host
|
9
|
+
attr_reader :protocol
|
10
|
+
|
5
11
|
# Supported values for options:
|
6
12
|
# :region - AWS region (e.g. us-west-1)
|
7
13
|
# :secure - true or false, default true.
|
8
|
-
def initialize(
|
9
|
-
@access_key =
|
10
|
-
@secret_key =
|
11
|
-
@
|
14
|
+
def initialize(access, secret, options = {})
|
15
|
+
@access_key = access
|
16
|
+
@secret_key = secret
|
17
|
+
@host = options[:region] ? "elasticmapreduce.#{options[:region]}.amazonaws.com" : 'elasticmapreduce.amazonaws.com'
|
18
|
+
@protocol = {:secure => true}.merge(options)[:secure] ? 'https' : 'http'
|
12
19
|
end
|
13
20
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
21
|
+
def submit(ruby_params)
|
22
|
+
aws_params = AwsRequest.convert_ruby_to_aws(ruby_params)
|
23
|
+
signed_params = sign_params(aws_params)
|
24
|
+
begin
|
25
|
+
RestClient.post("#@protocol://#@host", signed_params, :content_type => 'application/x-www-form-urlencoded; charset=utf-8')
|
26
|
+
rescue RestClient::BadRequest => e
|
27
|
+
raise ArgumentError, AwsRequest.parse_error_response(e.http_body)
|
28
|
+
end
|
29
|
+
end
|
17
30
|
|
18
|
-
|
19
|
-
|
20
|
-
|
31
|
+
def ==(other)
|
32
|
+
return false unless other.is_a? AwsRequest
|
33
|
+
return false unless @access_key == other.access_key
|
34
|
+
return false unless @secret_key == other.secret_key
|
35
|
+
return false unless @options == other.options
|
36
|
+
true
|
21
37
|
end
|
22
38
|
|
39
|
+
private
|
40
|
+
|
23
41
|
# (Used from RightScale's right_aws gem.)
|
24
42
|
# EC2, SQS, SDB and EMR requests must be signed by this guy.
|
25
43
|
# See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
|
26
44
|
# http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
|
27
|
-
def sign_params(service_hash
|
45
|
+
def sign_params(service_hash)
|
46
|
+
uri = '/' # TODO: Why are we hard-coding this?
|
28
47
|
service_hash["AWSAccessKeyId"] = @access_key
|
29
48
|
service_hash["Timestamp"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
30
49
|
service_hash["SignatureVersion"] = "2"
|
31
|
-
service_hash[
|
50
|
+
service_hash["SignatureMethod"] = "HmacSHA256"
|
32
51
|
canonical_string = service_hash.keys.sort.map do |key|
|
33
52
|
"#{AwsRequest.aws_escape(key)}=#{AwsRequest.aws_escape(service_hash[key])}"
|
34
53
|
end.join('&')
|
35
|
-
string_to_sign = "
|
54
|
+
string_to_sign = "POST\n#{@host.downcase}\n#{uri}\n#{canonical_string}"
|
36
55
|
signature = AwsRequest.aws_escape(Base64.encode64(OpenSSL::HMAC.digest("sha256", @secret_key, string_to_sign)).strip)
|
37
56
|
"#{canonical_string}&Signature=#{signature}"
|
38
57
|
end
|
39
58
|
|
40
|
-
|
59
|
+
# (Used from RightScale's right_aws gem)
|
60
|
+
# Escape a string according to Amazon's rules.
|
61
|
+
# See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
|
62
|
+
def self.aws_escape(param)
|
63
|
+
param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
|
64
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
65
|
+
end
|
66
|
+
end
|
41
67
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
68
|
+
# Since we use the same structure as AWS, we can generate AWS param names
|
69
|
+
# from the Ruby versions of those names (and the param nesting).
|
70
|
+
def self.convert_ruby_to_aws(params)
|
71
|
+
result = {}
|
72
|
+
params.each do |key, value|
|
73
|
+
case value
|
74
|
+
when Array
|
75
|
+
prefix = "#{camelize(key.to_s)}.member"
|
76
|
+
value.each_with_index do |item, index|
|
77
|
+
if item.is_a?(String)
|
78
|
+
result["#{prefix}.#{index+1}"] = item
|
79
|
+
else
|
80
|
+
convert_ruby_to_aws(item).each do |nested_key, nested_value|
|
81
|
+
result["#{prefix}.#{index+1}.#{nested_key}"] = nested_value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
when Hash
|
86
|
+
prefix = "#{camelize(key.to_s)}"
|
87
|
+
convert_ruby_to_aws(value).each do |nested_key, nested_value|
|
88
|
+
result["#{prefix}.#{nested_key}"] = nested_value
|
89
|
+
end
|
90
|
+
else
|
91
|
+
result[camelize(key.to_s)] = value
|
48
92
|
end
|
49
93
|
end
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
# (Used from Rails' ActiveSupport)
|
98
|
+
def self.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
99
|
+
if first_letter_in_uppercase
|
100
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
101
|
+
else
|
102
|
+
lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
|
103
|
+
end
|
104
|
+
end
|
50
105
|
|
106
|
+
# AWS error responses all follow the same form. Extract the message from
|
107
|
+
# the error document.
|
108
|
+
def self.parse_error_response(error_xml)
|
109
|
+
xml_doc = Nokogiri::XML(error_xml)
|
110
|
+
xml_doc.remove_namespaces!
|
111
|
+
xml_doc.xpath("/ErrorResponse/Error/Message").text
|
51
112
|
end
|
52
113
|
|
53
114
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Elasticity
|
2
|
+
|
3
|
+
class CustomJarStep
|
4
|
+
|
5
|
+
include JobFlowStep
|
6
|
+
|
7
|
+
attr_accessor :name
|
8
|
+
attr_accessor :jar
|
9
|
+
attr_accessor :arguments
|
10
|
+
attr_accessor :action_on_failure
|
11
|
+
|
12
|
+
def initialize(jar)
|
13
|
+
@name = "Elasticity Custom Jar Step (#{jar})"
|
14
|
+
@jar = jar
|
15
|
+
@arguments = []
|
16
|
+
@action_on_failure = 'TERMINATE_JOB_FLOW'
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_aws_step(job_flow)
|
20
|
+
step = {
|
21
|
+
:action_on_failure => @action_on_failure,
|
22
|
+
:hadoop_jar_step => {
|
23
|
+
:jar => @jar
|
24
|
+
},
|
25
|
+
:name => @name
|
26
|
+
}
|
27
|
+
step[:hadoop_jar_step][:args] = @arguments unless @arguments.empty?
|
28
|
+
step
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/elasticity/emr.rb
CHANGED
@@ -2,6 +2,8 @@ module Elasticity
|
|
2
2
|
|
3
3
|
class EMR
|
4
4
|
|
5
|
+
attr_reader :aws_request
|
6
|
+
|
5
7
|
def initialize(aws_access_key_id, aws_secret_access_key, options = {})
|
6
8
|
@aws_request = Elasticity::AwsRequest.new(aws_access_key_id, aws_secret_access_key, options)
|
7
9
|
end
|
@@ -12,18 +14,14 @@ module Elasticity
|
|
12
14
|
#
|
13
15
|
# Raises ArgumentError if the specified jobflow does not exist.
|
14
16
|
def describe_jobflow(jobflow_id)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
JobFlow.from_members_nodeset(xml_doc.xpath("/DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows/member")).first
|
24
|
-
rescue RestClient::BadRequest => e
|
25
|
-
raise ArgumentError, EMR.parse_error_response(e.http_body)
|
26
|
-
end
|
17
|
+
aws_result = @aws_request.submit({
|
18
|
+
:operation => 'DescribeJobFlows',
|
19
|
+
:job_flow_ids => [jobflow_id]
|
20
|
+
})
|
21
|
+
xml_doc = Nokogiri::XML(aws_result)
|
22
|
+
xml_doc.remove_namespaces!
|
23
|
+
yield aws_result if block_given?
|
24
|
+
JobFlowStatus.from_members_nodeset(xml_doc.xpath('/DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows/member')).first
|
27
25
|
end
|
28
26
|
|
29
27
|
# Lists all jobflows in all states.
|
@@ -33,13 +31,13 @@ module Elasticity
|
|
33
31
|
#
|
34
32
|
# describe_jobflows(:CreatedBefore => "2011-10-04")
|
35
33
|
def describe_jobflows(params = {})
|
36
|
-
aws_result = @aws_request.
|
37
|
-
params.merge({:operation =>
|
34
|
+
aws_result = @aws_request.submit(
|
35
|
+
params.merge({:operation => 'DescribeJobFlows'})
|
38
36
|
)
|
39
37
|
xml_doc = Nokogiri::XML(aws_result)
|
40
38
|
xml_doc.remove_namespaces!
|
41
39
|
yield aws_result if block_given?
|
42
|
-
|
40
|
+
JobFlowStatus.from_members_nodeset(xml_doc.xpath('/DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows/member'))
|
43
41
|
end
|
44
42
|
|
45
43
|
# Adds a new group of instances to the specified jobflow. Elasticity maps a
|
@@ -62,23 +60,19 @@ module Elasticity
|
|
62
60
|
# ["ig-2GOVEN6HVJZID", "ig-1DU9M2UQMM051", "ig-3DZRW4Y2X4S", ...]
|
63
61
|
def add_instance_groups(jobflow_id, instance_group_configs)
|
64
62
|
params = {
|
65
|
-
:operation =>
|
63
|
+
:operation => 'AddInstanceGroups',
|
66
64
|
:job_flow_id => jobflow_id,
|
67
65
|
:instance_groups => instance_group_configs
|
68
66
|
}
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
instance_group_ids << member.text
|
76
|
-
end
|
77
|
-
yield aws_result if block_given?
|
78
|
-
instance_group_ids
|
79
|
-
rescue RestClient::BadRequest => e
|
80
|
-
raise ArgumentError, EMR.parse_error_response(e.http_body)
|
67
|
+
aws_result = @aws_request.submit(params)
|
68
|
+
xml_doc = Nokogiri::XML(aws_result)
|
69
|
+
xml_doc.remove_namespaces!
|
70
|
+
instance_group_ids = []
|
71
|
+
xml_doc.xpath('/AddInstanceGroupsResponse/AddInstanceGroupsResult/InstanceGroupIds/member').each do |member|
|
72
|
+
instance_group_ids << member.text
|
81
73
|
end
|
74
|
+
yield aws_result if block_given?
|
75
|
+
instance_group_ids
|
82
76
|
end
|
83
77
|
|
84
78
|
# Add a step (or steps) to the specified job flow.
|
@@ -102,15 +96,11 @@ module Elasticity
|
|
102
96
|
# })
|
103
97
|
def add_jobflow_steps(jobflow_id, steps_config)
|
104
98
|
params = {
|
105
|
-
:operation =>
|
99
|
+
:operation => 'AddJobFlowSteps',
|
106
100
|
:job_flow_id => jobflow_id
|
107
101
|
}.merge!(steps_config)
|
108
|
-
|
109
|
-
|
110
|
-
yield aws_result if block_given?
|
111
|
-
rescue RestClient::BadRequest => e
|
112
|
-
raise ArgumentError, EMR.parse_error_response(e.http_body)
|
113
|
-
end
|
102
|
+
aws_result = @aws_request.submit(params)
|
103
|
+
yield aws_result if block_given?
|
114
104
|
end
|
115
105
|
|
116
106
|
# Set the number of instances in the specified instance groups to the
|
@@ -123,15 +113,11 @@ module Elasticity
|
|
123
113
|
# {"ig-1" => 40, "ig-2" => 5, ...}
|
124
114
|
def modify_instance_groups(instance_group_config)
|
125
115
|
params = {
|
126
|
-
:operation =>
|
116
|
+
:operation => 'ModifyInstanceGroups',
|
127
117
|
:instance_groups => instance_group_config.map { |k, v| {:instance_group_id => k, :instance_count => v} }
|
128
118
|
}
|
129
|
-
|
130
|
-
|
131
|
-
yield aws_result if block_given?
|
132
|
-
rescue RestClient::BadRequest => e
|
133
|
-
raise ArgumentError, EMR.parse_error_response(e.http_body)
|
134
|
-
end
|
119
|
+
aws_result = @aws_request.submit(params)
|
120
|
+
yield aws_result if block_given?
|
135
121
|
end
|
136
122
|
|
137
123
|
# Start a job flow with the specified configuration. This is a very thin
|
@@ -191,17 +177,13 @@ module Elasticity
|
|
191
177
|
# })
|
192
178
|
def run_job_flow(job_flow_config)
|
193
179
|
params = {
|
194
|
-
:operation =>
|
180
|
+
:operation => 'RunJobFlow',
|
195
181
|
}.merge!(job_flow_config)
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
xml_doc.xpath("/RunJobFlowResponse/RunJobFlowResult/JobFlowId").text
|
202
|
-
rescue RestClient::BadRequest => e
|
203
|
-
raise ArgumentError, EMR.parse_error_response(e.http_body)
|
204
|
-
end
|
182
|
+
aws_result = @aws_request.submit(params)
|
183
|
+
yield aws_result if block_given?
|
184
|
+
xml_doc = Nokogiri::XML(aws_result)
|
185
|
+
xml_doc.remove_namespaces!
|
186
|
+
xml_doc.xpath('/RunJobFlowResponse/RunJobFlowResult/JobFlowId').text
|
205
187
|
end
|
206
188
|
|
207
189
|
# Enabled or disable "termination protection" on the specified job flows.
|
@@ -214,16 +196,12 @@ module Elasticity
|
|
214
196
|
# ["j-1B4D1XP0C0A35", "j-1YG2MYL0HVYS5", ...]
|
215
197
|
def set_termination_protection(jobflow_ids, protection_enabled=true)
|
216
198
|
params = {
|
217
|
-
:operation =>
|
199
|
+
:operation => 'SetTerminationProtection',
|
218
200
|
:termination_protected => protection_enabled,
|
219
201
|
:job_flow_ids => jobflow_ids
|
220
202
|
}
|
221
|
-
|
222
|
-
|
223
|
-
yield aws_result if block_given?
|
224
|
-
rescue RestClient::BadRequest => e
|
225
|
-
raise ArgumentError, EMR.parse_error_response(e.http_body)
|
226
|
-
end
|
203
|
+
aws_result = @aws_request.submit(params)
|
204
|
+
yield aws_result if block_given?
|
227
205
|
end
|
228
206
|
|
229
207
|
# Terminate the specified jobflow. Amazon does not define a return value
|
@@ -232,74 +210,24 @@ module Elasticity
|
|
232
210
|
# flow does not exist.
|
233
211
|
def terminate_jobflows(jobflow_id)
|
234
212
|
params = {
|
235
|
-
:operation =>
|
213
|
+
:operation => 'TerminateJobFlows',
|
236
214
|
:job_flow_ids => [jobflow_id]
|
237
215
|
}
|
238
|
-
|
239
|
-
|
240
|
-
yield aws_result if block_given?
|
241
|
-
rescue RestClient::BadRequest
|
242
|
-
raise ArgumentError, "Job flow '#{jobflow_id}' does not exist."
|
243
|
-
end
|
216
|
+
aws_result = @aws_request.submit(params)
|
217
|
+
yield aws_result if block_given?
|
244
218
|
end
|
245
219
|
|
246
220
|
# Pass the specified params hash directly through to the AWS request URL.
|
247
221
|
# Use this if you want to perform an operation that hasn't yet been wrapped
|
248
222
|
# by Elasticity or you just want to see the response XML for yourself :)
|
249
223
|
def direct(params)
|
250
|
-
@aws_request.
|
224
|
+
@aws_request.submit(params)
|
251
225
|
end
|
252
226
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
# AWS error responses all follow the same form. Extract the message from
|
258
|
-
# the error document.
|
259
|
-
def parse_error_response(error_xml)
|
260
|
-
xml_doc = Nokogiri::XML(error_xml)
|
261
|
-
xml_doc.remove_namespaces!
|
262
|
-
xml_doc.xpath("/ErrorResponse/Error/Message").text
|
263
|
-
end
|
264
|
-
|
265
|
-
# Since we use the same structure as AWS, we can generate AWS param names
|
266
|
-
# from the Ruby versions of those names (and the param nesting).
|
267
|
-
def convert_ruby_to_aws(params)
|
268
|
-
result = {}
|
269
|
-
params.each do |key, value|
|
270
|
-
case value
|
271
|
-
when Array
|
272
|
-
prefix = "#{camelize(key.to_s)}.member"
|
273
|
-
value.each_with_index do |item, index|
|
274
|
-
if item.is_a?(String)
|
275
|
-
result["#{prefix}.#{index+1}"] = item
|
276
|
-
else
|
277
|
-
convert_ruby_to_aws(item).each do |nested_key, nested_value|
|
278
|
-
result["#{prefix}.#{index+1}.#{nested_key}"] = nested_value
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
when Hash
|
283
|
-
prefix = "#{camelize(key.to_s)}"
|
284
|
-
convert_ruby_to_aws(value).each do |nested_key, nested_value|
|
285
|
-
result["#{prefix}.#{nested_key}"] = nested_value
|
286
|
-
end
|
287
|
-
else
|
288
|
-
result[camelize(key.to_s)] = value
|
289
|
-
end
|
290
|
-
end
|
291
|
-
result
|
292
|
-
end
|
293
|
-
|
294
|
-
# (Used from Rails' ActiveSupport)
|
295
|
-
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
296
|
-
if first_letter_in_uppercase
|
297
|
-
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
298
|
-
else
|
299
|
-
lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
227
|
+
def ==(other)
|
228
|
+
return false unless other.is_a? EMR
|
229
|
+
return false unless @aws_request == other.aws_request
|
230
|
+
true
|
303
231
|
end
|
304
232
|
|
305
233
|
end
|