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
@@ -0,0 +1,23 @@
|
|
1
|
+
module Elasticity
|
2
|
+
|
3
|
+
module ConditionalRaising
|
4
|
+
|
5
|
+
def raise_if(conditional, error_class, message)
|
6
|
+
raise error_class, message if conditional
|
7
|
+
end
|
8
|
+
|
9
|
+
def raise_unless(conditional, error_class, message)
|
10
|
+
raise error_class, message unless conditional
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
module Kernel
|
18
|
+
include Elasticity::ConditionalRaising
|
19
|
+
end
|
20
|
+
|
21
|
+
class Object
|
22
|
+
include Kernel
|
23
|
+
end
|
data/lib/elasticity/version.rb
CHANGED
@@ -1,72 +1,180 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe Elasticity::AwsRequest do
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# Percent encode all other characters with %XY, where X and Y are hex characters 0-9 and uppercase A-F.
|
12
|
-
Elasticity::AwsRequest.aws_escape("foo$&+,/:;=?@bar").should == "foo%24%26%2B%2C%2F%3A%3B%3D%3F%40bar"
|
13
|
-
end
|
3
|
+
before do
|
4
|
+
Time.stub(:now).and_return(Time.at(1302461096))
|
5
|
+
end
|
6
|
+
|
7
|
+
subject do
|
8
|
+
Elasticity::AwsRequest.new('access', 'secret')
|
14
9
|
end
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
its(:access_key) { should == 'access' }
|
12
|
+
its(:secret_key) { should == 'secret' }
|
13
|
+
|
14
|
+
describe '#host' do
|
15
|
+
|
16
|
+
context 'when the region is not specified' do
|
17
|
+
its(:host) { should == 'elasticmapreduce.amazonaws.com' }
|
19
18
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
|
20
|
+
context 'when the region is specified' do
|
21
|
+
let(:request_with_region) do
|
22
|
+
Elasticity::AwsRequest.new('_', '_', {:region => 'us-west-1'})
|
23
|
+
end
|
24
|
+
it 'should incorporate the region into the hostname' do
|
25
|
+
request_with_region.host.should == 'elasticmapreduce.us-west-1.amazonaws.com'
|
26
|
+
end
|
24
27
|
end
|
28
|
+
|
25
29
|
end
|
26
30
|
|
27
|
-
describe
|
28
|
-
|
29
|
-
|
31
|
+
describe '#protocol' do
|
32
|
+
|
33
|
+
context 'when :secure is not specified' do
|
34
|
+
let(:default_request) { Elasticity::AwsRequest.new('_', '_') }
|
35
|
+
it 'should be https by default' do
|
36
|
+
default_request.protocol.should == 'https'
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
|
-
|
40
|
+
context 'when :secure is specified' do
|
33
41
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
:region => "eu-west-1")
|
39
|
-
RestClient.should_receive(:get).with(/elasticmapreduce\.eu\-west\-1\.amazonaws\.com/)
|
40
|
-
request.aws_emr_request({})
|
41
|
-
end
|
42
|
-
end
|
43
|
-
context "when :region is not specified" do
|
44
|
-
it "should use the default request url" do
|
45
|
-
request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_access_key")
|
46
|
-
RestClient.should_receive(:get).with(/elasticmapreduce\.amazonaws\.com/)
|
47
|
-
request.aws_emr_request({})
|
48
|
-
end
|
42
|
+
context 'when :secure is truthy' do
|
43
|
+
let(:secure_request) { Elasticity::AwsRequest.new('_', '_', {:secure => true}) }
|
44
|
+
it 'should be https' do
|
45
|
+
secure_request.protocol.should == 'https'
|
49
46
|
end
|
50
47
|
end
|
51
48
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
:secure => false)
|
57
|
-
RestClient.should_receive(:get).with(/^http:/)
|
58
|
-
request.aws_emr_request({})
|
59
|
-
end
|
49
|
+
context 'when :secure is falsey' do
|
50
|
+
let(:insecure_request) { Elasticity::AwsRequest.new('_', '_', {:secure => false}) }
|
51
|
+
it 'should be http' do
|
52
|
+
insecure_request.protocol.should == 'http'
|
60
53
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#sign_params' do
|
61
|
+
it 'should sign according to AWS rules' do
|
62
|
+
signed_params = subject.send(:sign_params, {})
|
63
|
+
signed_params.should == 'AWSAccessKeyId=access&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2011-04-10T18%3A44%3A56.000Z&Signature=QwQIiizWrfvWuLNnzmCMfaeXFfh9IQTvOix5MNVTh2s%3D'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#submit' do
|
68
|
+
|
69
|
+
let(:request) do
|
70
|
+
Elasticity::AwsRequest.new('_', '_').tap do |r|
|
71
|
+
r.instance_variable_set(:@host, 'HOSTNAME')
|
72
|
+
r.instance_variable_set(:@protocol, 'PROTOCOL')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should POST a properly assembled request' do
|
77
|
+
ruby_params = {}
|
78
|
+
aws_params = {}
|
79
|
+
Elasticity::AwsRequest.should_receive(:convert_ruby_to_aws).with(ruby_params).and_return(ruby_params)
|
80
|
+
request.should_receive(:sign_params).with(aws_params).and_return('SIGNED_PARAMS')
|
81
|
+
RestClient.should_receive(:post).with('PROTOCOL://HOSTNAME', 'SIGNED_PARAMS', :content_type => 'application/x-www-form-urlencoded; charset=utf-8')
|
82
|
+
request.submit(ruby_params)
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when there is an EMR error with the request' do
|
86
|
+
let(:error_message) { 'ERROR_MESSAGE' }
|
87
|
+
let(:error_xml) do
|
88
|
+
<<-ERROR
|
89
|
+
<ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
90
|
+
<Error>
|
91
|
+
<Message>#{error_message}</Message>
|
92
|
+
</Error>
|
93
|
+
</ErrorResponse>
|
94
|
+
ERROR
|
95
|
+
end
|
96
|
+
let(:error) do
|
97
|
+
RestClient::BadRequest.new.tap do |error|
|
98
|
+
error.stub(:http_body => error_xml)
|
67
99
|
end
|
68
100
|
end
|
69
101
|
|
102
|
+
it 'should raise an Argument error with the body of the error' do
|
103
|
+
RestClient.should_receive(:post).and_raise(error)
|
104
|
+
expect {
|
105
|
+
request.submit({})
|
106
|
+
}.to raise_error(ArgumentError, error_message)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#==' do
|
113
|
+
let(:same_object) { subject }
|
114
|
+
let(:same_values) { Elasticity::AwsRequest.new('access', 'secret', {}) }
|
115
|
+
let(:diff_type) { Object.new }
|
116
|
+
|
117
|
+
it { should == same_object }
|
118
|
+
it { should == same_values }
|
119
|
+
it { should_not == diff_type }
|
120
|
+
|
121
|
+
it 'should be false on deep comparison' do
|
122
|
+
{
|
123
|
+
:@access_key => '_',
|
124
|
+
:@secret_key => '_',
|
125
|
+
:@options => {:foo => :bar}
|
126
|
+
}.each do |variable, value|
|
127
|
+
other = Elasticity::AwsRequest.new('aws_access_key_id', 'aws_secret_access_key', {})
|
128
|
+
other.instance_variable_set(variable, value)
|
129
|
+
subject.should_not == other
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '.convert_ruby_to_aws' do
|
136
|
+
it 'should convert the params' do
|
137
|
+
add_jobflow_steps_params = {
|
138
|
+
:job_flow_id => 'j-1',
|
139
|
+
:steps => [
|
140
|
+
{
|
141
|
+
:action_on_failure => 'CONTINUE',
|
142
|
+
:name => 'First New Job Step',
|
143
|
+
:hadoop_jar_step => {
|
144
|
+
:args => ['arg1', 'arg2', 'arg3',],
|
145
|
+
:jar => 'first_step.jar',
|
146
|
+
:main_class => 'first_class.jar'
|
147
|
+
}
|
148
|
+
},
|
149
|
+
{
|
150
|
+
:action_on_failure => 'CANCEL_AND_WAIT',
|
151
|
+
:name => 'Second New Job Step',
|
152
|
+
:hadoop_jar_step => {
|
153
|
+
:args => ['arg4', 'arg5', 'arg6',],
|
154
|
+
:jar => 'second_step.jar',
|
155
|
+
:main_class => 'second_class.jar'
|
156
|
+
}
|
157
|
+
}
|
158
|
+
]
|
159
|
+
}
|
160
|
+
expected_result = {
|
161
|
+
'JobFlowId' => 'j-1',
|
162
|
+
'Steps.member.1.Name' => 'First New Job Step',
|
163
|
+
'Steps.member.1.ActionOnFailure' => 'CONTINUE',
|
164
|
+
'Steps.member.1.HadoopJarStep.Jar' => 'first_step.jar',
|
165
|
+
'Steps.member.1.HadoopJarStep.MainClass' => 'first_class.jar',
|
166
|
+
'Steps.member.1.HadoopJarStep.Args.member.1' => 'arg1',
|
167
|
+
'Steps.member.1.HadoopJarStep.Args.member.2' => 'arg2',
|
168
|
+
'Steps.member.1.HadoopJarStep.Args.member.3' => 'arg3',
|
169
|
+
'Steps.member.2.Name' => 'Second New Job Step',
|
170
|
+
'Steps.member.2.ActionOnFailure' => 'CANCEL_AND_WAIT',
|
171
|
+
'Steps.member.2.HadoopJarStep.Jar' => 'second_step.jar',
|
172
|
+
'Steps.member.2.HadoopJarStep.MainClass' => 'second_class.jar',
|
173
|
+
'Steps.member.2.HadoopJarStep.Args.member.1' => 'arg4',
|
174
|
+
'Steps.member.2.HadoopJarStep.Args.member.2' => 'arg5',
|
175
|
+
'Steps.member.2.HadoopJarStep.Args.member.3' => 'arg6'
|
176
|
+
}
|
177
|
+
Elasticity::AwsRequest.send(:convert_ruby_to_aws, add_jobflow_steps_params).should == expected_result
|
70
178
|
end
|
71
179
|
end
|
72
180
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
describe Elasticity::CustomJarStep do
|
2
|
+
|
3
|
+
subject do
|
4
|
+
Elasticity::CustomJarStep.new('jar')
|
5
|
+
end
|
6
|
+
|
7
|
+
it { should be_a Elasticity::JobFlowStep }
|
8
|
+
|
9
|
+
its(:name) { should == 'Elasticity Custom Jar Step (jar)' }
|
10
|
+
its(:jar) { should == 'jar' }
|
11
|
+
its(:arguments) { should == [] }
|
12
|
+
its(:action_on_failure) { should == 'TERMINATE_JOB_FLOW' }
|
13
|
+
|
14
|
+
describe '#to_aws_step' do
|
15
|
+
|
16
|
+
it { should respond_to(:to_aws_step).with(1).argument }
|
17
|
+
|
18
|
+
context 'when there are no arguments provided' do
|
19
|
+
let(:cjs_with_no_args) { Elasticity::CustomJarStep.new('jar') }
|
20
|
+
|
21
|
+
it 'should convert to aws step format' do
|
22
|
+
cjs_with_no_args.to_aws_step(Elasticity::JobFlow.new('access', 'secret')).should == {
|
23
|
+
:action_on_failure => 'TERMINATE_JOB_FLOW',
|
24
|
+
:hadoop_jar_step => {
|
25
|
+
:jar => 'jar'
|
26
|
+
},
|
27
|
+
:name => 'Elasticity Custom Jar Step (jar)'
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when there are arguments provided' do
|
33
|
+
let(:cjs_with_args) do
|
34
|
+
Elasticity::CustomJarStep.new('jar').tap do |cjs|
|
35
|
+
cjs.arguments = ['arg1', 'arg2']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should convert to aws step format' do
|
40
|
+
cjs_with_args.to_aws_step(Elasticity::JobFlow.new('access', 'secret')).should == {
|
41
|
+
:action_on_failure => 'TERMINATE_JOB_FLOW',
|
42
|
+
:hadoop_jar_step => {
|
43
|
+
:jar => 'jar',
|
44
|
+
:args => ['arg1', 'arg2',],
|
45
|
+
},
|
46
|
+
:name => 'Elasticity Custom Jar Step (jar)'
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '.requires_installation?' do
|
54
|
+
it 'should not require installation' do
|
55
|
+
Elasticity::CustomJarStep.requires_installation?.should be_false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -1,879 +1,348 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe Elasticity::EMR do
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
context "when properly specified" do
|
10
|
-
use_vcr_cassette "add_instance_groups/one_group_successful", :record => :none
|
11
|
-
it "should add the instance groups" do
|
12
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
13
|
-
instance_group_config = {
|
14
|
-
:instance_count => 1,
|
15
|
-
:instance_role => "TASK",
|
16
|
-
:instance_type => "m1.small",
|
17
|
-
:market => "ON_DEMAND",
|
18
|
-
:name => "Go Canucks Go!"
|
19
|
-
}
|
20
|
-
instance_group_ids = emr.add_instance_groups("j-OALI7TZTQMHX", [instance_group_config])
|
21
|
-
instance_group_ids.should == ["ig-2GOVEN6HVJZID"]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context "when improperly specified" do
|
26
|
-
use_vcr_cassette "add_instance_groups/one_group_unsuccessful", :record => :none
|
27
|
-
it "should add the instance groups" do
|
28
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
29
|
-
instance_group_config = {
|
30
|
-
:bid_price => 0,
|
31
|
-
:instance_count => 1,
|
32
|
-
:instance_role => "TASK",
|
33
|
-
:instance_type => "m1.small",
|
34
|
-
:market => "ON_DEMAND",
|
35
|
-
:name => "Go Canucks Go!"
|
36
|
-
}
|
37
|
-
lambda {
|
38
|
-
emr.add_instance_groups("j-19WDDS68ZUENP", [instance_group_config])
|
39
|
-
}.should raise_error(ArgumentError, "Task instance group already exists in the job flow, cannot add more task groups")
|
40
|
-
end
|
41
|
-
end
|
3
|
+
subject do
|
4
|
+
Elasticity::EMR.new('ACCESS', 'SECRET')
|
5
|
+
end
|
42
6
|
|
43
|
-
|
7
|
+
its(:aws_request) { should == Elasticity::AwsRequest.new('ACCESS', 'SECRET', {}) }
|
44
8
|
|
45
|
-
|
46
|
-
|
47
|
-
context "when multiple instance groups are specified" do
|
48
|
-
before do
|
49
|
-
@add_instance_groups_xml = <<-ADD_GROUPS
|
50
|
-
<AddInstanceGroupsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
51
|
-
<AddInstanceGroupsResult>
|
52
|
-
<JobFlowId>j-OALI7TZTQMHX</JobFlowId>
|
53
|
-
<InstanceGroupIds>
|
54
|
-
<member>ig-1</member>
|
55
|
-
<member>ig-2</member>
|
56
|
-
<member>ig-3</member>
|
57
|
-
</InstanceGroupIds>
|
58
|
-
</AddInstanceGroupsResult>
|
59
|
-
</AddInstanceGroupsResponse>
|
60
|
-
ADD_GROUPS
|
61
|
-
end
|
9
|
+
describe '#add_instance_groups' do
|
62
10
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
"InstanceGroups.member.1.Name"=>"Go Canucks Go!",
|
72
|
-
"InstanceGroups.member.1.InstanceRole"=>"CORE",
|
73
|
-
"InstanceGroups.member.1.InstanceCount"=>1,
|
74
|
-
"InstanceGroups.member.1.BidPrice"=>0,
|
75
|
-
"InstanceGroups.member.1.InstanceType"=>"m1.small",
|
76
|
-
"InstanceGroups.member.1.Market"=>"ON_DEMAND",
|
77
|
-
"InstanceGroups.member.2.Name"=>"Go Canucks Go!",
|
78
|
-
"InstanceGroups.member.2.InstanceRole"=>"CORE",
|
79
|
-
"InstanceGroups.member.2.InstanceCount"=>1,
|
80
|
-
"InstanceGroups.member.2.BidPrice"=>0,
|
81
|
-
"InstanceGroups.member.2.InstanceType"=>"m1.small",
|
82
|
-
"InstanceGroups.member.2.Market"=>"ON_DEMAND",
|
83
|
-
"JobFlowId"=>"j-19WDDS68ZUENP"
|
84
|
-
})
|
85
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
86
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
87
|
-
emr.add_instance_groups("j-19WDDS68ZUENP", instance_group_configs)
|
88
|
-
end
|
11
|
+
it 'should send the correct params to AWS' do
|
12
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
13
|
+
:operation => 'AddInstanceGroups',
|
14
|
+
:job_flow_id => 'JOBFLOW_ID',
|
15
|
+
:instance_groups => ['INSTANCE_GROUP_CONFIGS']
|
16
|
+
})
|
17
|
+
subject.add_instance_groups('JOBFLOW_ID', ['INSTANCE_GROUP_CONFIGS'])
|
18
|
+
end
|
89
19
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
20
|
+
describe 'return values' do
|
21
|
+
let(:aws_response) do
|
22
|
+
<<-XML
|
23
|
+
<AddInstanceGroupsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
24
|
+
<AddInstanceGroupsResult>
|
25
|
+
<JobFlowId>j-OALI7TZTQMHX</JobFlowId>
|
26
|
+
<InstanceGroupIds>
|
27
|
+
<member>ig-1</member>
|
28
|
+
<member>ig-2</member>
|
29
|
+
<member>ig-3</member>
|
30
|
+
</InstanceGroupIds>
|
31
|
+
</AddInstanceGroupsResult>
|
32
|
+
</AddInstanceGroupsResponse>
|
33
|
+
XML
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should return an array of the new instance groups IDs' do
|
37
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(aws_response)
|
38
|
+
subject.add_instance_groups('', []).should == ['ig-1', 'ig-2', 'ig-3']
|
97
39
|
end
|
40
|
+
end
|
98
41
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
xml_result = nil
|
106
|
-
emr.add_instance_groups("", []) do |xml|
|
107
|
-
xml_result = xml
|
108
|
-
end
|
109
|
-
xml_result.should == "AWS XML"
|
42
|
+
context 'when a block is given' do
|
43
|
+
let(:result) { 'RESULT' }
|
44
|
+
it 'should yield the submission results' do
|
45
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
46
|
+
subject.add_instance_groups('', []) do |xml|
|
47
|
+
xml.should == 'RESULT'
|
110
48
|
end
|
111
49
|
end
|
112
|
-
|
113
50
|
end
|
114
51
|
|
115
52
|
end
|
116
53
|
|
117
|
-
describe
|
118
|
-
|
119
|
-
describe "integration happy path" do
|
120
|
-
use_vcr_cassette "add_jobflow_steps/add_multiple_steps", :record => :none
|
121
|
-
|
122
|
-
before do
|
123
|
-
@setup_pig_step = {
|
124
|
-
:action_on_failure => "TERMINATE_JOB_FLOW",
|
125
|
-
:hadoop_jar_step => {
|
126
|
-
:args => [
|
127
|
-
"s3://elasticmapreduce/libs/pig/pig-script",
|
128
|
-
"--base-path",
|
129
|
-
"s3://elasticmapreduce/libs/pig/",
|
130
|
-
"--install-pig"
|
131
|
-
],
|
132
|
-
:jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
|
133
|
-
},
|
134
|
-
:name => "Setup Pig"
|
135
|
-
}
|
136
|
-
@emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
137
|
-
@jobflow_id = @emr.run_job_flow({
|
138
|
-
:name => "Elasticity Test Flow (EMR Pig Script)",
|
139
|
-
:instances => {
|
140
|
-
:ec2_key_name => "sharethrough-dev",
|
141
|
-
:instance_count => 2,
|
142
|
-
:master_instance_type => "m1.small",
|
143
|
-
:slave_instance_type => "m1.small",
|
144
|
-
},
|
145
|
-
:steps => [@setup_pig_step]
|
146
|
-
})
|
147
|
-
end
|
148
|
-
|
149
|
-
it "should add a job flow step to the specified job flow" do
|
150
|
-
@emr.add_jobflow_steps(@jobflow_id, {
|
151
|
-
:steps => [
|
152
|
-
@setup_pig_step.merge(:name => "Setup Pig 2"),
|
153
|
-
@setup_pig_step.merge(:name => "Setup Pig 3")
|
154
|
-
]
|
155
|
-
})
|
156
|
-
jobflow = @emr.describe_jobflows.select { |jf| jf.jobflow_id = @jobflow_id }.first
|
157
|
-
jobflow.steps.map(&:name).should == ["Setup Pig", "Setup Pig 2", "Setup Pig 3"]
|
158
|
-
end
|
54
|
+
describe '#add_jobflow_steps' do
|
159
55
|
|
56
|
+
it 'should add the specified steps to the job flow' do
|
57
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
58
|
+
:operation => 'AddJobFlowSteps',
|
59
|
+
:job_flow_id => 'JOBFLOW_ID',
|
60
|
+
:steps => ['_']
|
61
|
+
})
|
62
|
+
subject.add_jobflow_steps('JOBFLOW_ID', {:steps => ['_']})
|
160
63
|
end
|
161
64
|
|
162
|
-
|
163
|
-
|
164
|
-
it
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
"JobFlowId" => "j-1",
|
169
|
-
"Steps.member.1.Name" => "Step 1",
|
170
|
-
"Steps.member.1.ActionOnFailure" => "TERMINATE_JOB_FLOW",
|
171
|
-
"Steps.member.1.HadoopJarStep.Jar" => "jar1",
|
172
|
-
"Steps.member.1.HadoopJarStep.Args.member.1" => "arg1-1",
|
173
|
-
"Steps.member.1.HadoopJarStep.Args.member.2" => "arg1-2",
|
174
|
-
"Steps.member.2.Name" => "Step 2",
|
175
|
-
"Steps.member.2.ActionOnFailure" => "CONTINUE",
|
176
|
-
"Steps.member.2.HadoopJarStep.Jar" => "jar2",
|
177
|
-
"Steps.member.2.HadoopJarStep.Args.member.1" => "arg2-1",
|
178
|
-
"Steps.member.2.HadoopJarStep.Args.member.2" => "arg2-2",
|
179
|
-
})
|
180
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
181
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
182
|
-
emr.add_jobflow_steps("j-1", {
|
183
|
-
:steps => [
|
184
|
-
{
|
185
|
-
:action_on_failure => "TERMINATE_JOB_FLOW",
|
186
|
-
:name => "Step 1",
|
187
|
-
:hadoop_jar_step => {
|
188
|
-
:args => ["arg1-1", "arg1-2"],
|
189
|
-
:jar => "jar1",
|
190
|
-
}
|
191
|
-
},
|
192
|
-
{
|
193
|
-
:action_on_failure => "CONTINUE",
|
194
|
-
:name => "Step 2",
|
195
|
-
:hadoop_jar_step => {
|
196
|
-
:args => ["arg2-1", "arg2-2"],
|
197
|
-
:jar => "jar2",
|
198
|
-
}
|
199
|
-
}
|
200
|
-
]
|
201
|
-
})
|
202
|
-
end
|
203
|
-
|
204
|
-
context "when there is an error" do
|
205
|
-
before do
|
206
|
-
@error_message = "2 validation errors detected: Value null at 'steps' failed to satisfy constraint: Member must not be null; Value null at 'jobFlowId' failed to satisfy constraint: Member must not be null"
|
207
|
-
@error_xml = <<-ERROR
|
208
|
-
<ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
209
|
-
<Error>
|
210
|
-
<Message>#{@error_message}</Message>
|
211
|
-
</Error>
|
212
|
-
</ErrorResponse>
|
213
|
-
ERROR
|
214
|
-
end
|
215
|
-
|
216
|
-
it "should raise an ArgumentError with the error message" do
|
217
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
218
|
-
@exception = RestClient::BadRequest.new
|
219
|
-
@exception.should_receive(:http_body).and_return(@error_xml)
|
220
|
-
aws_request.should_receive(:aws_emr_request).and_raise(@exception)
|
221
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
222
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
223
|
-
lambda {
|
224
|
-
emr.add_jobflow_steps("", {})
|
225
|
-
}.should raise_error(ArgumentError, @error_message)
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
|
-
context "when a block is given" do
|
230
|
-
it "should yield the XML result" do
|
231
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
232
|
-
aws_request.should_receive(:aws_emr_request).and_return("xml_response")
|
233
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
234
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
235
|
-
xml_result = nil
|
236
|
-
emr.add_jobflow_steps("", {}) do |xml|
|
237
|
-
xml_result = xml
|
238
|
-
end
|
239
|
-
xml_result.should == "xml_response"
|
65
|
+
context 'when a block is given' do
|
66
|
+
let(:result) { 'RESULT' }
|
67
|
+
it 'should yield the submission results' do
|
68
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
69
|
+
subject.add_jobflow_steps('', {}) do |xml|
|
70
|
+
xml.should == 'RESULT'
|
240
71
|
end
|
241
72
|
end
|
242
|
-
|
243
|
-
|
244
73
|
end
|
245
74
|
|
246
75
|
end
|
247
76
|
|
248
|
-
describe
|
77
|
+
describe '#describe_jobflows' do
|
249
78
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
79
|
+
let(:describe_jobflows_xml) do
|
80
|
+
<<-XML
|
81
|
+
<DescribeJobFlowsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
82
|
+
<DescribeJobFlowsResult>
|
83
|
+
<JobFlows>
|
84
|
+
<member>
|
85
|
+
<ExecutionStatusDetail>
|
86
|
+
<CreationDateTime>2011-04-04T17:41:51Z</CreationDateTime>
|
87
|
+
<State>TERMINATED</State>
|
88
|
+
</ExecutionStatusDetail>
|
89
|
+
<JobFlowId>j-p</JobFlowId>
|
90
|
+
<Name>Pig Job</Name>
|
91
|
+
</member>
|
92
|
+
<member>
|
93
|
+
<ExecutionStatusDetail>
|
94
|
+
<State>TERMINATED</State>
|
95
|
+
<CreationDateTime>2011-04-04T17:41:51Z</CreationDateTime>
|
96
|
+
</ExecutionStatusDetail>
|
97
|
+
<JobFlowId>j-h</JobFlowId>
|
98
|
+
<Name>Hive Job</Name>
|
99
|
+
</member>
|
100
|
+
</JobFlows>
|
101
|
+
</DescribeJobFlowsResult>
|
102
|
+
</DescribeJobFlowsResponse>
|
103
|
+
XML
|
259
104
|
end
|
260
105
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
<member>
|
268
|
-
<ExecutionStatusDetail>
|
269
|
-
<CreationDateTime>2011-04-04T17:41:51Z</CreationDateTime>
|
270
|
-
<State>TERMINATED</State>
|
271
|
-
</ExecutionStatusDetail>
|
272
|
-
<JobFlowId>j-p</JobFlowId>
|
273
|
-
<Name>Pig Job</Name>
|
274
|
-
</member>
|
275
|
-
<member>
|
276
|
-
<ExecutionStatusDetail>
|
277
|
-
<State>TERMINATED</State>
|
278
|
-
<CreationDateTime>2011-04-04T17:41:51Z</CreationDateTime>
|
279
|
-
</ExecutionStatusDetail>
|
280
|
-
<JobFlowId>j-h</JobFlowId>
|
281
|
-
<Name>Hive Job</Name>
|
282
|
-
</member>
|
283
|
-
</JobFlows>
|
284
|
-
</DescribeJobFlowsResult>
|
285
|
-
</DescribeJobFlowsResponse>
|
286
|
-
JOBFLOWS
|
287
|
-
end
|
106
|
+
it 'should return an array of properly populated JobFlowStatusES' do
|
107
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(describe_jobflows_xml)
|
108
|
+
jobflow_statuses = subject.describe_jobflows
|
109
|
+
jobflow_statuses.map(&:name).should == ['Pig Job', 'Hive Job']
|
110
|
+
jobflow_statuses.map(&:class).should == [Elasticity::JobFlowStatus, Elasticity::JobFlowStatus]
|
111
|
+
end
|
288
112
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
jobflows.map(&:name).should == ["Pig Job", "Hive Job"]
|
296
|
-
end
|
113
|
+
it 'should describe all jobflows' do
|
114
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
115
|
+
:operation => 'DescribeJobFlows'
|
116
|
+
})
|
117
|
+
subject.describe_jobflows
|
118
|
+
end
|
297
119
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
120
|
+
context 'when additional parameters are provided' do
|
121
|
+
it 'should pass them through' do
|
122
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
123
|
+
:CreatedBefore => '2011-10-04',
|
124
|
+
:operation => 'DescribeJobFlows'
|
125
|
+
})
|
126
|
+
subject.describe_jobflows(:CreatedBefore => '2011-10-04')
|
304
127
|
end
|
128
|
+
end
|
305
129
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
xml_result = nil
|
313
|
-
emr.describe_jobflows do |xml|
|
314
|
-
xml_result = xml
|
315
|
-
end
|
316
|
-
xml_result.should == "describe!"
|
130
|
+
context 'when a block is given' do
|
131
|
+
let(:result) { 'RESULT' }
|
132
|
+
it 'should yield the submission results' do
|
133
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
134
|
+
subject.describe_jobflows do |xml|
|
135
|
+
xml.should == 'RESULT'
|
317
136
|
end
|
318
137
|
end
|
319
138
|
end
|
320
139
|
|
321
140
|
end
|
322
141
|
|
323
|
-
describe
|
324
|
-
|
325
|
-
|
142
|
+
describe '#describe_jobflow' do
|
143
|
+
|
144
|
+
let(:describe_jobflows_xml) {
|
145
|
+
<<-XML
|
326
146
|
<DescribeJobFlowsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
327
147
|
<DescribeJobFlowsResult>
|
328
148
|
<JobFlows>
|
329
149
|
<member>
|
150
|
+
<JobFlowId>j-3UN6WX5RRO2AG</JobFlowId>
|
151
|
+
<Name>The One Job Flow</Name>
|
330
152
|
<ExecutionStatusDetail>
|
331
153
|
<State>TERMINATED</State>
|
332
154
|
<CreationDateTime>2011-04-04T17:41:51Z</CreationDateTime>
|
333
155
|
</ExecutionStatusDetail>
|
334
|
-
<JobFlowId>j-3UN6WX5RRO2AG</JobFlowId>
|
335
|
-
<Name>The One Job Flow</Name>
|
336
156
|
</member>
|
337
157
|
</JobFlows>
|
338
158
|
</DescribeJobFlowsResult>
|
339
159
|
</DescribeJobFlowsResponse>
|
340
|
-
|
341
|
-
|
160
|
+
XML
|
161
|
+
}
|
342
162
|
|
343
|
-
it
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
"JobFlowIds.member.1" => "j-3UN6WX5RRO2AG"
|
163
|
+
it 'should describe the specified jobflow' do
|
164
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
165
|
+
:operation => 'DescribeJobFlows',
|
166
|
+
:job_flow_ids => ['j-3UN6WX5RRO2AG']
|
348
167
|
})
|
349
|
-
|
350
|
-
emr = Elasticity::EMR.new("", "")
|
351
|
-
emr.describe_jobflow("j-3UN6WX5RRO2AG")
|
352
|
-
end
|
353
|
-
|
354
|
-
context "when the job flow ID exists" do
|
355
|
-
it "should return a JobFlow" do
|
356
|
-
aws_request = Elasticity::AwsRequest.new("","")
|
357
|
-
aws_request.stub(:aws_emr_request).with({
|
358
|
-
"Operation" => "DescribeJobFlows",
|
359
|
-
"JobFlowIds.member.1" => "j-3UN6WX5RRO2AG"
|
360
|
-
}).and_return(@describe_jobflows_xml)
|
361
|
-
Elasticity::AwsRequest.stub(:new).and_return(aws_request)
|
362
|
-
emr = Elasticity::EMR.new("", "")
|
363
|
-
jobflow = emr.describe_jobflow("j-3UN6WX5RRO2AG")
|
364
|
-
jobflow.jobflow_id.should == "j-3UN6WX5RRO2AG"
|
365
|
-
end
|
168
|
+
subject.describe_jobflow('j-3UN6WX5RRO2AG')
|
366
169
|
end
|
367
170
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
<Message>Specified job flow ID not valid</Message>
|
374
|
-
</Error>
|
375
|
-
</ErrorResponse>
|
376
|
-
ERROR
|
377
|
-
end
|
378
|
-
|
379
|
-
it "should raise an ArgumentError with the error message" do
|
380
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
381
|
-
@exception = RestClient::BadRequest.new
|
382
|
-
@exception.should_receive(:http_body).and_return(@error_xml)
|
383
|
-
aws_request.should_receive(:aws_emr_request).and_raise(@exception)
|
384
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
385
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
386
|
-
lambda {
|
387
|
-
emr.describe_jobflow("bad_jobflow_id")
|
388
|
-
}.should raise_error(ArgumentError, "Specified job flow ID not valid")
|
389
|
-
end
|
171
|
+
it 'should return a properly populated JobFlowStatus' do
|
172
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(describe_jobflows_xml)
|
173
|
+
jobflow_status = subject.describe_jobflow('_')
|
174
|
+
jobflow_status.should be_a Elasticity::JobFlowStatus
|
175
|
+
jobflow_status.jobflow_id.should == 'j-3UN6WX5RRO2AG'
|
390
176
|
end
|
391
177
|
|
392
|
-
context
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
xml_result = nil
|
399
|
-
emr.describe_jobflow("_") do |xml|
|
400
|
-
xml_result = xml
|
178
|
+
context 'when a block is given' do
|
179
|
+
let(:result) { 'RESULT' }
|
180
|
+
it 'should yield the submission results' do
|
181
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
182
|
+
subject.describe_jobflow('') do |xml|
|
183
|
+
xml.should == 'RESULT'
|
401
184
|
end
|
402
|
-
xml_result.should == "describe!"
|
403
185
|
end
|
404
186
|
end
|
405
187
|
end
|
406
188
|
|
407
|
-
describe
|
189
|
+
describe '#modify_instance_groups' do
|
408
190
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
191
|
+
it 'should modify the specified instance groups' do
|
192
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
193
|
+
:operation => 'ModifyInstanceGroups',
|
194
|
+
:instance_groups => [{
|
195
|
+
:instance_group_id => 'ig-2T1HNUO61BG3O',
|
196
|
+
:instance_count => 2
|
197
|
+
}]
|
198
|
+
})
|
199
|
+
subject.modify_instance_groups({'ig-2T1HNUO61BG3O' => 2})
|
418
200
|
end
|
419
201
|
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
"Operation" => "ModifyInstanceGroups",
|
427
|
-
"InstanceGroups.member.1.InstanceGroupId" => "ig-1",
|
428
|
-
"InstanceGroups.member.1.InstanceCount" => 2
|
429
|
-
})
|
430
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
431
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
432
|
-
emr.modify_instance_groups({"ig-1" => 2})
|
202
|
+
context 'when a block is given' do
|
203
|
+
let(:result) { '_' }
|
204
|
+
it 'should yield the submission results' do
|
205
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
206
|
+
subject.modify_instance_groups({}) do |xml|
|
207
|
+
xml.should == '_'
|
433
208
|
end
|
434
209
|
end
|
435
|
-
|
436
|
-
context "when a block is given" do
|
437
|
-
it "should yield the XML result" do
|
438
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
439
|
-
aws_request.should_receive(:aws_emr_request).and_return("xml result!")
|
440
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
441
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
442
|
-
xml_result = nil
|
443
|
-
emr.modify_instance_groups({"ig-1" => 2}) do |xml|
|
444
|
-
xml_result = xml
|
445
|
-
end
|
446
|
-
xml_result.should == "xml result!"
|
447
|
-
end
|
448
|
-
end
|
449
|
-
|
450
|
-
|
451
|
-
context "when there is an error" do
|
452
|
-
|
453
|
-
before do
|
454
|
-
@error_message = "1 validation error detected: Value null at 'instanceGroups.1.member.instanceCount' failed to satisfy constraint: Member must not be null"
|
455
|
-
@error_xml = <<-ERROR
|
456
|
-
<ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
457
|
-
<Error>
|
458
|
-
<Message>#{@error_message}</Message>
|
459
|
-
</Error>
|
460
|
-
</ErrorResponse>
|
461
|
-
ERROR
|
462
|
-
end
|
463
|
-
|
464
|
-
it "should raise an ArgumentError with the error message" do
|
465
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
466
|
-
@exception = RestClient::BadRequest.new
|
467
|
-
@exception.should_receive(:http_body).and_return(@error_xml)
|
468
|
-
aws_request.should_receive(:aws_emr_request).and_raise(@exception)
|
469
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
470
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
471
|
-
lambda {
|
472
|
-
emr.modify_instance_groups({"ig-1" => 2})
|
473
|
-
}.should raise_error(ArgumentError, @error_message)
|
474
|
-
end
|
475
|
-
|
476
|
-
end
|
477
|
-
|
478
210
|
end
|
479
211
|
|
480
212
|
end
|
481
213
|
|
482
|
-
describe
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
jobflow_id = emr.run_job_flow({
|
491
|
-
:name => "Elasticity Test Flow (EMR Pig Script)",
|
492
|
-
:instances => {
|
493
|
-
:ec2_key_name => "sharethrough-dev",
|
494
|
-
:hadoop_version => "0.20",
|
495
|
-
:instance_count => 2,
|
496
|
-
:master_instance_type => "m1.small",
|
497
|
-
:placement => {
|
498
|
-
:availability_zone => "us-east-1a"
|
499
|
-
},
|
500
|
-
:slave_instance_type => "m1.small",
|
501
|
-
},
|
502
|
-
:steps => [
|
503
|
-
{
|
504
|
-
:action_on_failure => "TERMINATE_JOB_FLOW",
|
505
|
-
:hadoop_jar_step => {
|
506
|
-
:args => [
|
507
|
-
"s3://elasticmapreduce/libs/pig/pig-script",
|
508
|
-
"--base-path",
|
509
|
-
"s3://elasticmapreduce/libs/pig/",
|
510
|
-
"--install-pig"
|
511
|
-
],
|
512
|
-
:jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
|
513
|
-
},
|
514
|
-
:name => "Setup Pig"
|
515
|
-
},
|
516
|
-
{
|
517
|
-
:action_on_failure => "TERMINATE_JOB_FLOW",
|
518
|
-
:hadoop_jar_step => {
|
519
|
-
:args => [
|
520
|
-
"s3://elasticmapreduce/libs/pig/pig-script",
|
521
|
-
"--run-pig-script",
|
522
|
-
"--args",
|
523
|
-
"-p",
|
524
|
-
"INPUT=s3n://elasticmapreduce/samples/pig-apache/input",
|
525
|
-
"-p",
|
526
|
-
"OUTPUT=s3n://slif-elasticity/pig-apache/output/2011-04-19",
|
527
|
-
"s3n://elasticmapreduce/samples/pig-apache/do-reports.pig"
|
528
|
-
],
|
529
|
-
:jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar"
|
530
|
-
},
|
531
|
-
:name => "Run Pig Script"
|
532
|
-
}
|
533
|
-
]
|
534
|
-
})
|
535
|
-
jobflow_id.should == "j-G6N5HA528AD4"
|
536
|
-
end
|
537
|
-
end
|
214
|
+
describe '#run_jobflow' do
|
215
|
+
|
216
|
+
it 'should start the specified job flow' do
|
217
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
218
|
+
:operation => 'RunJobFlow',
|
219
|
+
:jobflow_params => '_'
|
220
|
+
})
|
221
|
+
subject.run_job_flow({:jobflow_params => '_'})
|
538
222
|
end
|
539
223
|
|
540
|
-
describe
|
541
|
-
|
542
|
-
|
224
|
+
describe 'jobflow response handling' do
|
225
|
+
let(:jobflow_xml_response) do
|
226
|
+
<<-XML
|
543
227
|
<RunJobFlowResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
544
228
|
<RunJobFlowResult>
|
545
|
-
<JobFlowId>j-
|
229
|
+
<JobFlowId>j-G6N5HA528AD4</JobFlowId>
|
546
230
|
</RunJobFlowResult>
|
547
231
|
<ResponseMetadata>
|
548
|
-
<RequestId>
|
232
|
+
<RequestId>b22f4aea-6a4b-11e0-9ddc-a168e244afdb</RequestId>
|
549
233
|
</ResponseMetadata>
|
550
234
|
</RunJobFlowResponse>
|
551
|
-
|
552
|
-
aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
553
|
-
aws_request.should_receive(:aws_emr_request).and_return(run_jobflow_response)
|
554
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
555
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
556
|
-
jobflow_id = emr.run_job_flow({})
|
557
|
-
jobflow_id.should == "j-N500G8Y8U7ZQ"
|
558
|
-
end
|
559
|
-
|
560
|
-
it "should run the specified job flow" do
|
561
|
-
aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
562
|
-
aws_request.should_receive(:aws_emr_request).with({
|
563
|
-
"Operation" => "RunJobFlow",
|
564
|
-
"Name" => "Job flow name",
|
565
|
-
"Instances.MasterInstanceType" => "m1.small",
|
566
|
-
"Instances.Placement.AvailabilityZone" => "us-east-1a",
|
567
|
-
"Steps.member.1.Name" => "Streaming Job",
|
568
|
-
"Steps.member.1.ActionOnFailure" => "TERMINATE_JOB_FLOW",
|
569
|
-
"Steps.member.1.HadoopJarStep.Jar" => "/home/hadoop/contrib/streaming/hadoop-streaming.jar",
|
570
|
-
"Steps.member.1.HadoopJarStep.Args.member.1" => "-input",
|
571
|
-
"Steps.member.1.HadoopJarStep.Args.member.2" => "s3n://elasticmapreduce/samples/wordcount/input"
|
572
|
-
})
|
573
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
574
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
575
|
-
emr.run_job_flow({
|
576
|
-
:name => "Job flow name",
|
577
|
-
:instances => {
|
578
|
-
:master_instance_type => "m1.small",
|
579
|
-
:placement => {
|
580
|
-
:availability_zone => "us-east-1a"
|
581
|
-
}
|
582
|
-
},
|
583
|
-
:steps => [
|
584
|
-
{
|
585
|
-
:action_on_failure => "TERMINATE_JOB_FLOW",
|
586
|
-
:name => "Streaming Job",
|
587
|
-
:hadoop_jar_step => {
|
588
|
-
:args => ["-input", "s3n://elasticmapreduce/samples/wordcount/input"],
|
589
|
-
:jar => "/home/hadoop/contrib/streaming/hadoop-streaming.jar",
|
590
|
-
}
|
591
|
-
}
|
592
|
-
]
|
593
|
-
})
|
235
|
+
XML
|
594
236
|
end
|
595
237
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
@error_xml = <<-ERROR
|
600
|
-
<ErrorResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
601
|
-
<Error>
|
602
|
-
<Message>#{@error_message}</Message>
|
603
|
-
</Error>
|
604
|
-
</ErrorResponse>
|
605
|
-
ERROR
|
606
|
-
end
|
607
|
-
|
608
|
-
it "should raise an ArgumentError with the error message" do
|
609
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
610
|
-
@exception = RestClient::BadRequest.new
|
611
|
-
@exception.should_receive(:http_body).and_return(@error_xml)
|
612
|
-
aws_request.should_receive(:aws_emr_request).and_raise(@exception)
|
613
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
614
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
615
|
-
lambda {
|
616
|
-
emr.run_job_flow({})
|
617
|
-
}.should raise_error(ArgumentError, @error_message)
|
618
|
-
end
|
238
|
+
it 'should return the ID of the running job flow' do
|
239
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(jobflow_xml_response)
|
240
|
+
subject.run_job_flow({}).should == 'j-G6N5HA528AD4'
|
619
241
|
end
|
242
|
+
end
|
620
243
|
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
xml_result = nil
|
628
|
-
emr.run_job_flow({}) do |xml|
|
629
|
-
xml_result = xml
|
630
|
-
end
|
631
|
-
xml_result.should == "jobflow_id!"
|
244
|
+
context 'when a block is given' do
|
245
|
+
let(:result) { '_' }
|
246
|
+
it 'should yield the submission results' do
|
247
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
248
|
+
subject.run_job_flow({}) do |xml|
|
249
|
+
xml.should == '_'
|
632
250
|
end
|
633
251
|
end
|
634
252
|
end
|
635
253
|
|
636
254
|
end
|
637
255
|
|
638
|
-
describe
|
256
|
+
describe '#terminate_jobflows' do
|
639
257
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
end
|
647
|
-
end
|
258
|
+
it 'should terminate the specific jobflow' do
|
259
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
260
|
+
:operation => 'TerminateJobFlows',
|
261
|
+
:job_flow_ids => ['j-1']
|
262
|
+
})
|
263
|
+
subject.terminate_jobflows('j-1')
|
648
264
|
end
|
649
265
|
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
<ResponseMetadata>
|
657
|
-
<RequestId>2690d7eb-ed86-11dd-9877-6fad448a8419</RequestId>
|
658
|
-
</ResponseMetadata>
|
659
|
-
</TerminateJobFlowsResponse>
|
660
|
-
RESPONSE
|
661
|
-
end
|
662
|
-
it "should terminate the specific jobflow" do
|
663
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
664
|
-
aws_request.should_receive(:aws_emr_request).with({
|
665
|
-
"Operation" => "TerminateJobFlows",
|
666
|
-
"JobFlowIds.member.1" => "j-1"
|
667
|
-
}).and_return(@terminate_jobflows_xml)
|
668
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
669
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
670
|
-
emr.terminate_jobflows("j-1")
|
671
|
-
end
|
672
|
-
end
|
673
|
-
|
674
|
-
context "when the jobflow does not exist" do
|
675
|
-
it "should raise an ArgumentError" do
|
676
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
677
|
-
aws_request.should_receive(:aws_emr_request).and_raise(RestClient::BadRequest)
|
678
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
679
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
680
|
-
lambda {
|
681
|
-
emr.terminate_jobflows("invalid_jobflow_id")
|
682
|
-
}.should raise_error(ArgumentError)
|
683
|
-
end
|
684
|
-
end
|
685
|
-
|
686
|
-
context "when a block is given" do
|
687
|
-
it "should yield the XML result" do
|
688
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
689
|
-
aws_request.should_receive(:aws_emr_request).and_return("terminated!")
|
690
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
691
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
692
|
-
xml_result = nil
|
693
|
-
emr.terminate_jobflows("j-1") do |xml|
|
694
|
-
xml_result = xml
|
695
|
-
end
|
696
|
-
xml_result.should == "terminated!"
|
266
|
+
context 'when a block is given' do
|
267
|
+
let(:result) { '_' }
|
268
|
+
it 'should yield the termination results' do
|
269
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
270
|
+
subject.terminate_jobflows('j-1') do |xml|
|
271
|
+
xml.should == '_'
|
697
272
|
end
|
698
273
|
end
|
699
|
-
|
700
274
|
end
|
701
|
-
end
|
702
|
-
|
703
|
-
describe "#set_termination_protection" do
|
704
|
-
|
705
|
-
describe "integration happy path" do
|
706
|
-
|
707
|
-
context "when protecting multiple job flows" do
|
708
|
-
use_vcr_cassette "set_termination_protection/protect_multiple_job_flows", :record => :none
|
709
|
-
it "should protect the specified job flows" do
|
710
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
711
|
-
emr.set_termination_protection(["j-1B4D1XP0C0A35", "j-1YG2MYL0HVYS5"], true)
|
712
|
-
end
|
713
|
-
end
|
714
275
|
|
715
|
-
|
716
|
-
use_vcr_cassette "set_termination_protection/nonexistent_job_flows", :record => :none
|
717
|
-
it "should have an error" do
|
718
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
719
|
-
lambda {
|
720
|
-
emr.set_termination_protection(["j-1B4D1XP0C0A35", "j-2"], true)
|
721
|
-
}.should raise_error(ArgumentError, "Specified job flow ID not valid")
|
722
|
-
end
|
723
|
-
end
|
276
|
+
end
|
724
277
|
|
725
|
-
|
278
|
+
describe '#set_termination_protection' do
|
726
279
|
|
727
|
-
|
728
|
-
it
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
"JobFlowIds.member.1" => "jobflow1",
|
734
|
-
"JobFlowIds.member.2" => "jobflow2",
|
735
|
-
"TerminationProtected" => true
|
280
|
+
context 'when protection is enabled' do
|
281
|
+
it 'should enable protection on the specified jobflows' do
|
282
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
283
|
+
:operation => 'SetTerminationProtection',
|
284
|
+
:termination_protected => true,
|
285
|
+
:job_flow_ids => ['jobflow1', 'jobflow2']
|
736
286
|
})
|
737
|
-
|
738
|
-
emr.set_termination_protection(["jobflow1", "jobflow2"], true)
|
287
|
+
subject.set_termination_protection(['jobflow1', 'jobflow2'], true)
|
739
288
|
end
|
289
|
+
end
|
740
290
|
|
741
|
-
|
742
|
-
|
743
|
-
Elasticity::AwsRequest.should_receive(:
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
"JobFlowIds.member.2" => "jobflow2",
|
748
|
-
"TerminationProtected" => false
|
291
|
+
context 'when protection is disabled' do
|
292
|
+
it 'should disable protection on the specified jobflows' do
|
293
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
294
|
+
:operation => 'SetTerminationProtection',
|
295
|
+
:termination_protected => false,
|
296
|
+
:job_flow_ids => ['jobflow1', 'jobflow2']
|
749
297
|
})
|
750
|
-
|
751
|
-
emr.set_termination_protection(["jobflow1", "jobflow2"], false)
|
298
|
+
subject.set_termination_protection(['jobflow1', 'jobflow2'], false)
|
752
299
|
end
|
300
|
+
end
|
753
301
|
|
754
|
-
|
755
|
-
|
756
|
-
Elasticity::AwsRequest.should_receive(:
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
"JobFlowIds.member.2" => "jobflow2",
|
761
|
-
"TerminationProtected" => true
|
302
|
+
context 'when protection is not specified' do
|
303
|
+
it 'should enable protection on the specified jobflows' do
|
304
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with({
|
305
|
+
:operation => 'SetTerminationProtection',
|
306
|
+
:termination_protected => true,
|
307
|
+
:job_flow_ids => ['jobflow1', 'jobflow2']
|
762
308
|
})
|
763
|
-
|
764
|
-
emr.set_termination_protection(["jobflow1", "jobflow2"])
|
309
|
+
subject.set_termination_protection(['jobflow1', 'jobflow2'])
|
765
310
|
end
|
311
|
+
end
|
766
312
|
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
</ResponseMetadata>
|
774
|
-
</SetTerminationProtectionResponse>
|
775
|
-
RESPONSE
|
776
|
-
end
|
777
|
-
it "should yield the XML result" do
|
778
|
-
aws_request = Elasticity::AwsRequest.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
779
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
780
|
-
aws_request.should_receive(:aws_emr_request).and_return(@xml_response)
|
781
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
782
|
-
xml = nil
|
783
|
-
emr.set_termination_protection([]) do |aws_response|
|
784
|
-
xml = aws_response
|
785
|
-
end
|
786
|
-
xml.should == @xml_response
|
313
|
+
context 'when a block is given' do
|
314
|
+
let(:result) { '_' }
|
315
|
+
it 'should yield the termination results' do
|
316
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).and_return(result)
|
317
|
+
subject.set_termination_protection([]) do |xml|
|
318
|
+
xml.should == '_'
|
787
319
|
end
|
788
320
|
end
|
789
321
|
end
|
790
322
|
|
791
323
|
end
|
792
324
|
|
793
|
-
describe
|
794
|
-
|
795
|
-
describe "integration happy path" do
|
796
|
-
use_vcr_cassette "direct/terminate_jobflow", :record => :none
|
797
|
-
it "should terminate the specified jobflow" do
|
798
|
-
emr = Elasticity::EMR.new(AWS_ACCESS_KEY_ID, AWS_SECRET_KEY)
|
799
|
-
params = {
|
800
|
-
"Operation" => "TerminateJobFlows",
|
801
|
-
"JobFlowIds.member.1" => "j-1MZ5TVWFJRSKN"
|
802
|
-
}
|
803
|
-
emr.direct(params)
|
804
|
-
end
|
805
|
-
end
|
325
|
+
describe '#direct' do
|
326
|
+
let(:params) { {:foo => 'bar'} }
|
806
327
|
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
<TerminateJobFlowsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
|
811
|
-
<ResponseMetadata>
|
812
|
-
<RequestId>2690d7eb-ed86-11dd-9877-6fad448a8419</RequestId>
|
813
|
-
</ResponseMetadata>
|
814
|
-
</TerminateJobFlowsResponse>
|
815
|
-
RESPONSE
|
816
|
-
end
|
817
|
-
it "should pass through directly to the request" do
|
818
|
-
aws_request = Elasticity::AwsRequest.new("aws_access_key_id", "aws_secret_key")
|
819
|
-
aws_request.should_receive(:aws_emr_request).with({
|
820
|
-
"Operation" => "TerminateJobFlows",
|
821
|
-
"JobFlowIds.member.1" => "j-1"
|
822
|
-
}).and_return(@terminate_jobflows_xml)
|
823
|
-
Elasticity::AwsRequest.should_receive(:new).and_return(aws_request)
|
824
|
-
emr = Elasticity::EMR.new("aws_access_key_id", "aws_secret_key")
|
825
|
-
params = {
|
826
|
-
"Operation" => "TerminateJobFlows",
|
827
|
-
"JobFlowIds.member.1" => "j-1"
|
828
|
-
}
|
829
|
-
emr.direct(params).should == @terminate_jobflows_xml
|
830
|
-
end
|
328
|
+
it 'should pass through directly to the request and return the results of the request' do
|
329
|
+
Elasticity::AwsRequest.any_instance.should_receive(:submit).with(params).and_return('RESULT')
|
330
|
+
subject.direct(params).should == 'RESULT'
|
831
331
|
end
|
832
332
|
end
|
833
333
|
|
834
|
-
describe
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
}
|
847
|
-
},
|
848
|
-
{
|
849
|
-
:action_on_failure => "CANCEL_AND_WAIT",
|
850
|
-
:name => "Second New Job Step",
|
851
|
-
:hadoop_jar_step => {
|
852
|
-
:args => ["arg4", "arg5", "arg6",],
|
853
|
-
:jar => "second_step.jar",
|
854
|
-
:main_class => "second_class.jar"
|
855
|
-
}
|
856
|
-
}
|
857
|
-
]
|
858
|
-
}
|
859
|
-
expected_result = {
|
860
|
-
"JobFlowId" => "j-1",
|
861
|
-
"Steps.member.1.Name" => "First New Job Step",
|
862
|
-
"Steps.member.1.ActionOnFailure" => "CONTINUE",
|
863
|
-
"Steps.member.1.HadoopJarStep.Jar" => "first_step.jar",
|
864
|
-
"Steps.member.1.HadoopJarStep.MainClass" => "first_class.jar",
|
865
|
-
"Steps.member.1.HadoopJarStep.Args.member.1" => "arg1",
|
866
|
-
"Steps.member.1.HadoopJarStep.Args.member.2" => "arg2",
|
867
|
-
"Steps.member.1.HadoopJarStep.Args.member.3" => "arg3",
|
868
|
-
"Steps.member.2.Name" => "Second New Job Step",
|
869
|
-
"Steps.member.2.ActionOnFailure" => "CANCEL_AND_WAIT",
|
870
|
-
"Steps.member.2.HadoopJarStep.Jar" => "second_step.jar",
|
871
|
-
"Steps.member.2.HadoopJarStep.MainClass" => "second_class.jar",
|
872
|
-
"Steps.member.2.HadoopJarStep.Args.member.1" => "arg4",
|
873
|
-
"Steps.member.2.HadoopJarStep.Args.member.2" => "arg5",
|
874
|
-
"Steps.member.2.HadoopJarStep.Args.member.3" => "arg6"
|
875
|
-
}
|
876
|
-
Elasticity::EMR.send(:convert_ruby_to_aws, add_jobflow_steps_params).should == expected_result
|
334
|
+
describe '#==' do
|
335
|
+
let(:emr1) { Elasticity::EMR.new('ACCESS1', 'SECRET1') }
|
336
|
+
let(:emr2) { Elasticity::EMR.new('ACCESS2', 'SECRET2') }
|
337
|
+
|
338
|
+
let(:same_object) { emr1 }
|
339
|
+
let(:same_values) { Elasticity::EMR.new('ACCESS1', 'SECRET1') }
|
340
|
+
let(:diff_type) { Object.new }
|
341
|
+
|
342
|
+
it 'should pass comparison checks' do
|
343
|
+
emr1.should == same_object
|
344
|
+
emr1.should == same_values
|
345
|
+
emr1.should_not == diff_type
|
877
346
|
end
|
878
347
|
end
|
879
348
|
|