wakoopa-elasticity 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.autotest +2 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +4 -0
  6. data/HISTORY.mediawiki +30 -0
  7. data/LICENSE +202 -0
  8. data/README.mediawiki +332 -0
  9. data/Rakefile +11 -0
  10. data/elasticity.gemspec +29 -0
  11. data/lib/elasticity.rb +16 -0
  12. data/lib/elasticity/aws_request.rb +52 -0
  13. data/lib/elasticity/emr.rb +282 -0
  14. data/lib/elasticity/hive_job.rb +71 -0
  15. data/lib/elasticity/job_flow.rb +53 -0
  16. data/lib/elasticity/job_flow_step.rb +36 -0
  17. data/lib/elasticity/pig_job.rb +112 -0
  18. data/lib/elasticity/simple_job.rb +50 -0
  19. data/lib/elasticity/version.rb +3 -0
  20. data/spec/fixtures/vcr_cassettes/add_instance_groups/one_group_successful.yml +38 -0
  21. data/spec/fixtures/vcr_cassettes/add_instance_groups/one_group_unsuccessful.yml +35 -0
  22. data/spec/fixtures/vcr_cassettes/add_jobflow_steps/add_multiple_steps.yml +252 -0
  23. data/spec/fixtures/vcr_cassettes/describe_jobflows/all_jobflows.yml +69 -0
  24. data/spec/fixtures/vcr_cassettes/direct/terminate_jobflow.yml +32 -0
  25. data/spec/fixtures/vcr_cassettes/hive_job/hive_ads.yml +35 -0
  26. data/spec/fixtures/vcr_cassettes/modify_instance_groups/set_instances_to_3.yml +32 -0
  27. data/spec/fixtures/vcr_cassettes/pig_job/apache_log_reports.yml +35 -0
  28. data/spec/fixtures/vcr_cassettes/pig_job/apache_log_reports_with_bootstrap.yml +35 -0
  29. data/spec/fixtures/vcr_cassettes/run_jobflow/word_count.yml +35 -0
  30. data/spec/fixtures/vcr_cassettes/set_termination_protection/nonexistent_job_flows.yml +35 -0
  31. data/spec/fixtures/vcr_cassettes/set_termination_protection/protect_multiple_job_flows.yml +32 -0
  32. data/spec/fixtures/vcr_cassettes/terminate_jobflows/one_jobflow.yml +32 -0
  33. data/spec/lib/elasticity/aws_request_spec.rb +62 -0
  34. data/spec/lib/elasticity/emr_spec.rb +794 -0
  35. data/spec/lib/elasticity/hive_job_spec.rb +96 -0
  36. data/spec/lib/elasticity/job_flow_spec.rb +139 -0
  37. data/spec/lib/elasticity/job_flow_step_spec.rb +76 -0
  38. data/spec/lib/elasticity/pig_job_spec.rb +211 -0
  39. data/spec/spec_helper.rb +43 -0
  40. metadata +253 -0
@@ -0,0 +1,53 @@
1
+ module Elasticity
2
+
3
+ class JobFlow
4
+
5
+ attr_accessor :name
6
+ attr_accessor :jobflow_id
7
+ attr_accessor :state
8
+ attr_accessor :steps
9
+ attr_accessor :created_at
10
+ attr_accessor :started_at
11
+ attr_accessor :ready_at
12
+ attr_accessor :instance_count
13
+ attr_accessor :master_instance_type
14
+ attr_accessor :slave_instance_type
15
+
16
+ def initialize
17
+ @steps = []
18
+ end
19
+
20
+ class << self
21
+
22
+ # Create a jobflow from an AWS <member> (Nokogiri::XML::Element):
23
+ # /DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows/member
24
+ def from_member_element(xml_element)
25
+ jobflow = JobFlow.new
26
+ jobflow.name = xml_element.xpath("./Name").text.strip
27
+ jobflow.jobflow_id = xml_element.xpath("./JobFlowId").text.strip
28
+ jobflow.state = xml_element.xpath("./ExecutionStatusDetail/State").text.strip
29
+ jobflow.steps = JobFlowStep.from_members_nodeset(xml_element.xpath("./Steps/member"))
30
+ jobflow.created_at = xml_element.xpath("./ExecutionStatusDetail/CreationDateTime").text.strip
31
+ jobflow.started_at = xml_element.xpath("./ExecutionStatusDetail/StartDateTime").text.strip
32
+ jobflow.ready_at = xml_element.xpath("./ExecutionStatusDetail/ReadyDateTime").text.strip
33
+ jobflow.instance_count = xml_element.xpath("./Instances/InstanceCount").text.strip
34
+ jobflow.master_instance_type = xml_element.xpath("./Instances/MasterInstanceType").text.strip
35
+ jobflow.slave_instance_type = xml_element.xpath("./Instances/SlaveInstanceType").text.strip
36
+ jobflow
37
+ end
38
+
39
+ # Create JobFlows from a collection of AWS <member> nodes (Nokogiri::XML::NodeSet):
40
+ # /DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows
41
+ def from_members_nodeset(members_nodeset)
42
+ jobflows = []
43
+ members_nodeset.each do |member|
44
+ jobflows << from_member_element(member)
45
+ end
46
+ jobflows
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,36 @@
1
+ module Elasticity
2
+
3
+ class JobFlowStep
4
+
5
+ attr_accessor :name
6
+ attr_accessor :state
7
+ attr_accessor :started_at
8
+ attr_accessor :ended_at
9
+
10
+ class << self
11
+
12
+ # Create a job flow from an AWS <member> (Nokogiri::XML::Element):
13
+ # /DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows/member/Steps/member
14
+ def from_member_element(xml_element)
15
+ job_flow_step = JobFlowStep.new
16
+ job_flow_step.name = xml_element.xpath("./StepConfig/Name").text.strip
17
+ job_flow_step.state = xml_element.xpath("./ExecutionStatusDetail/State").text.strip
18
+ job_flow_step.started_at = xml_element.xpath("./ExecutionStatusDetail/StartDateTime").text.strip
19
+ job_flow_step.ended_at = xml_element.xpath("./ExecutionStatusDetail/EndDateTime").text.strip
20
+ job_flow_step
21
+ end
22
+
23
+ # Create JobFlowSteps from a collection of AWS <member> nodes (Nokogiri::XML::NodeSet):
24
+ # /DescribeJobFlowsResponse/DescribeJobFlowsResult/JobFlows/member/Steps/member
25
+ def from_members_nodeset(members_nodeset)
26
+ jobflow_steps = []
27
+ members_nodeset.each do |member|
28
+ jobflow_steps << from_member_element(member)
29
+ end
30
+ jobflow_steps
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,112 @@
1
+ module Elasticity
2
+
3
+ class PigJob < Elasticity::SimpleJob
4
+
5
+ # Automatically passed as Pig argument E_PARALLELS
6
+ attr_reader :parallels
7
+
8
+ def initialize(aws_access_key_id, aws_secret_access_key)
9
+ super
10
+ @name = "Elasticity Pig Job"
11
+ @parallels = calculate_parallels
12
+ end
13
+
14
+ def instance_count=(num_instances)
15
+ if num_instances < 2
16
+ raise ArgumentError, "Instance count cannot be set to less than 2 (requested #{num_instances})"
17
+ end
18
+ @instance_count = num_instances
19
+ @parallels = calculate_parallels
20
+ end
21
+
22
+ def slave_instance_type=(instance_type)
23
+ @slave_instance_type = instance_type
24
+ @parallels = calculate_parallels
25
+ end
26
+
27
+ # Run the specified Pig script with the specified variables.
28
+ #
29
+ # pig = Elasticity::PigJob.new("access", "secret")
30
+ # jobflow_id = pig.run('s3n://slif-pig-test/test.pig', {
31
+ # 'SCRIPTS' => 's3n://slif-pig-test/scripts',
32
+ # 'OUTPUT' => 's3n://slif-pig-test/output',
33
+ # 'XREFS' => 's3n://slif-pig-test/xrefs'
34
+ # })
35
+ #
36
+ # The variables are accessible within your Pig scripts by using the
37
+ # standard ${NAME} syntax.
38
+ def run(pig_script, pig_variables={})
39
+ script_arguments = ["s3://elasticmapreduce/libs/pig/pig-script", "--run-pig-script", "--args"]
40
+ pig_variables.keys.sort.each do |variable_name|
41
+ script_arguments.concat(["-p", "#{variable_name}=#{pig_variables[variable_name]}"])
42
+ end
43
+ script_arguments.concat(["-p", "E_PARALLELS=#{@parallels}"])
44
+ script_arguments << pig_script
45
+ jobflow_config = {
46
+ :name => @name,
47
+ :instances => {
48
+ :ec2_key_name => @ec2_key_name,
49
+ :hadoop_version => @hadoop_version,
50
+ :instance_count => @instance_count,
51
+ :master_instance_type => @master_instance_type,
52
+ :slave_instance_type => @slave_instance_type,
53
+ },
54
+ :steps => [
55
+ {
56
+ :action_on_failure => "TERMINATE_JOB_FLOW",
57
+ :hadoop_jar_step => {
58
+ :jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar",
59
+ :args => [
60
+ "s3://elasticmapreduce/libs/pig/pig-script",
61
+ "--base-path", "s3://elasticmapreduce/libs/pig/",
62
+ "--install-pig"
63
+ ],
64
+ },
65
+ :name => "Setup Pig"
66
+ },
67
+ {
68
+ :action_on_failure => @action_on_failure,
69
+ :hadoop_jar_step => {
70
+ :jar => "s3://elasticmapreduce/libs/script-runner/script-runner.jar",
71
+ :args => script_arguments,
72
+ },
73
+ :name => "Run Pig Script"
74
+ }
75
+ ]
76
+ }
77
+
78
+ jobflow_config.merge!(:log_uri => @log_uri) if @log_uri
79
+ jobflow_config.merge!(get_bootstrap_actions)
80
+
81
+ @emr.run_job_flow(jobflow_config)
82
+ end
83
+
84
+ private
85
+
86
+ # Calculate a common-sense default value of PARALLELS using the following
87
+ # formula from the Pig Cookbook:
88
+ #
89
+ # <num machines> * <num reduce slots per machine> * 0.9
90
+ #
91
+ # With the following reducer configuration (from an AWS forum post):
92
+ #
93
+ # m1.small 1
94
+ # m1.large 2
95
+ # m1.xlarge 4
96
+ # c1.medium 2
97
+ # c1.xlarge 4
98
+ def calculate_parallels
99
+ reduce_slots = case @slave_instance_type
100
+ when "m1.small" then 1
101
+ when "m1.large" then 2
102
+ when "m1.xlarge" then 4
103
+ when "c1.medium" then 2
104
+ when "c1.xlarge" then 4
105
+ else 1
106
+ end
107
+ ((@instance_count - 1).to_f * reduce_slots.to_f * 0.9).ceil
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,50 @@
1
+ module Elasticity
2
+
3
+ class SimpleJob
4
+
5
+ attr_accessor :action_on_failure
6
+ attr_accessor :aws_access_key_id
7
+ attr_accessor :aws_secret_access_key
8
+ attr_accessor :ec2_key_name
9
+ attr_accessor :name
10
+ attr_accessor :hadoop_version
11
+ attr_accessor :instance_count
12
+ attr_accessor :log_uri
13
+ attr_accessor :master_instance_type
14
+ attr_accessor :slave_instance_type
15
+
16
+ def initialize(aws_access_key_id, aws_secret_access_key)
17
+ @action_on_failure = "TERMINATE_JOB_FLOW"
18
+ @aws_access_key_id = aws_access_key_id
19
+ @aws_secret_access_key = aws_secret_access_key
20
+ @ec2_key_name = "default"
21
+ @hadoop_version = "0.20"
22
+ @instance_count = 2
23
+ @master_instance_type = "m1.small"
24
+ @name = "Elasticity Job"
25
+ @slave_instance_type = "m1.small"
26
+
27
+ @emr = Elasticity::EMR.new(aws_access_key_id, aws_secret_access_key)
28
+ end
29
+
30
+ def add_hadoop_bootstrap_action(option, value)
31
+ @hadoop_actions ||= []
32
+ @hadoop_actions << {
33
+ :name => "Elasticity Bootstrap Action (Configure Hadoop)",
34
+ :script_bootstrap_action => {
35
+ :path => "s3n://elasticmapreduce/bootstrap-actions/configure-hadoop",
36
+ :args => [option, value]
37
+ }
38
+ }
39
+ end
40
+
41
+ private
42
+
43
+ def get_bootstrap_actions
44
+ return {} unless @hadoop_actions && !@hadoop_actions.empty?
45
+ { :bootstrap_actions => @hadoop_actions }
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ module Elasticity
2
+ VERSION = "1.2.3"
3
+ end
@@ -0,0 +1,38 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: !ruby/regexp /^https:\/\/elasticmapreduce.amazonaws.com:443\/\?AWSAccessKeyId=AKIAI7HEMMNKGT6VFFSA&InstanceGroups.member.1.InstanceCount=1&InstanceGroups.member.1.InstanceRole=TASK&InstanceGroups.member.1.InstanceType=m1.small&InstanceGroups.member.1.Market=ON_DEMAND&InstanceGroups.member.1.Name=Go%20Canucks%20Go!&JobFlowId=j-OALI7TZTQMHX&Operation=AddInstanceGroups/
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
+ - ddd0d158-67eb-11e0-ba06-2b5c43005be2
19
+ content-type:
20
+ - text/xml
21
+ date:
22
+ - Sat, 16 Apr 2011 05:39:15 GMT
23
+ content-length:
24
+ - "411"
25
+ body: |
26
+ <AddInstanceGroupsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
27
+ <AddInstanceGroupsResult>
28
+ <JobFlowId>j-OALI7TZTQMHX</JobFlowId>
29
+ <InstanceGroupIds>
30
+ <member>ig-2GOVEN6HVJZID</member>
31
+ </InstanceGroupIds>
32
+ </AddInstanceGroupsResult>
33
+ <ResponseMetadata>
34
+ <RequestId>ddd0d158-67eb-11e0-ba06-2b5c43005be2</RequestId>
35
+ </ResponseMetadata>
36
+ </AddInstanceGroupsResponse>
37
+
38
+ 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 /^https:\/\/elasticmapreduce.amazonaws.com:443\/\?AWSAccessKeyId=AKIAI7HEMMNKGT6VFFSA&InstanceGroups.member.1.BidPrice=0&InstanceGroups.member.1.InstanceCount=1&InstanceGroups.member.1.InstanceRole=TASK&InstanceGroups.member.1.InstanceType=m1.small&InstanceGroups.member.1.Market=ON_DEMAND&InstanceGroups.member.1.Name=Go%20Canucks%20Go!&JobFlowId=j-19WDDS68ZUENP&Operation=AddInstanceGroups/
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
+ - 0c8d744d-67ea-11e0-bf8a-ed57a5465c87
19
+ content-type:
20
+ - text/xml
21
+ date:
22
+ - Sat, 16 Apr 2011 05:26:15 GMT
23
+ content-length:
24
+ - "337"
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>Task instance group already exists in the job flow, cannot add more task groups</Message>
31
+ </Error>
32
+ <RequestId>0c8d744d-67ea-11e0-bf8a-ed57a5465c87</RequestId>
33
+ </ErrorResponse>
34
+
35
+ http_version: "1.1"
@@ -0,0 +1,252 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: !ruby/regexp /^https:\/\/elasticmapreduce.amazonaws.com:443\/\?.*Operation=RunJobFlow/
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
+ - 71b9eb69-6d61-11e0-9ddc-a168e244afdb
19
+ content-type:
20
+ - text/xml
21
+ date:
22
+ - Sat, 23 Apr 2011 04:23:30 GMT
23
+ content-length:
24
+ - "296"
25
+ body: |
26
+ <RunJobFlowResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
27
+ <RunJobFlowResult>
28
+ <JobFlowId>j-F0UCD5851OKV</JobFlowId>
29
+ </RunJobFlowResult>
30
+ <ResponseMetadata>
31
+ <RequestId>71b9eb69-6d61-11e0-9ddc-a168e244afdb</RequestId>
32
+ </ResponseMetadata>
33
+ </RunJobFlowResponse>
34
+
35
+ http_version: "1.1"
36
+ - !ruby/struct:VCR::HTTPInteraction
37
+ request: !ruby/struct:VCR::Request
38
+ method: :get
39
+ uri: !ruby/regexp /^https:\/\/elasticmapreduce.amazonaws.com:443\/\?.*Operation=AddJobFlowSteps/
40
+ body:
41
+ headers:
42
+ accept:
43
+ - "*/*; q=0.5, application/xml"
44
+ accept-encoding:
45
+ - gzip, deflate
46
+ response: !ruby/struct:VCR::Response
47
+ status: !ruby/struct:VCR::ResponseStatus
48
+ code: 200
49
+ message: OK
50
+ headers:
51
+ x-amzn-requestid:
52
+ - 71e1712c-6d61-11e0-b6c0-e9580d1f7304
53
+ content-type:
54
+ - text/xml
55
+ date:
56
+ - Sat, 23 Apr 2011 04:23:31 GMT
57
+ content-length:
58
+ - "221"
59
+ body: |
60
+ <AddJobFlowStepsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
61
+ <ResponseMetadata>
62
+ <RequestId>71e1712c-6d61-11e0-b6c0-e9580d1f7304</RequestId>
63
+ </ResponseMetadata>
64
+ </AddJobFlowStepsResponse>
65
+
66
+ http_version: "1.1"
67
+ - !ruby/struct:VCR::HTTPInteraction
68
+ request: !ruby/struct:VCR::Request
69
+ method: :get
70
+ uri: !ruby/regexp /^https:\/\/elasticmapreduce.amazonaws.com:443\/\?.*Operation=DescribeJobFlows/
71
+ body:
72
+ headers:
73
+ accept:
74
+ - "*/*; q=0.5, application/xml"
75
+ accept-encoding:
76
+ - gzip, deflate
77
+ response: !ruby/struct:VCR::Response
78
+ status: !ruby/struct:VCR::ResponseStatus
79
+ code: 200
80
+ message: OK
81
+ headers:
82
+ x-amzn-requestid:
83
+ - 720bb591-6d61-11e0-8835-2b8f31bf243b
84
+ content-type:
85
+ - text/xml
86
+ date:
87
+ - Sat, 23 Apr 2011 04:23:31 GMT
88
+ content-encoding:
89
+ - gzip
90
+ vary:
91
+ - Accept-Encoding
92
+ transfer-encoding:
93
+ - chunked
94
+ body: !binary |
95
+ H4sIAAAAAAAAAO1dWXPiSrJ+n1/Bnae5D2pq0Up4fINFbGaXWMxLhwC5zRwM
96
+ DEv3eH79LUnGBhlQIRCSoCJ6ek4jVVaqlJmV36eqrIecuRwuxgOzPBvkJ7M/
97
+ y5a5nM+mSzPxn7fJdPnPv7+uVvNUMmlOjOVqPHwz5gtztB6aP4w347+zqfFn
98
+ +WM4e0uOZsMkAkDhAOYw/Pvj3xKJhz2S15OVdYlc3Pzo/JP88Ga+DczF5p/k
99
+ B/U/5nC9Gs+m2spYrZc5c2WMJ1/XyR3ZhWlYN+SMlamP38xHBCDkAM8hrAM+
100
+ hXAKg/5D8ttt2zIs4eajpqdbeqlWeEg6//7SIumhxkNmNlstVwtjnh5ady2T
101
+ W9dqBulNdUZuvHpP6OZylbAeO/EPtdpKNMa/EhoZovnqfx+S9r1/29LLnC93
102
+ NHUP0Odt2dn0Zfxr9wK5VDRGs9m8bCyse9xXrTdgLB6XeM+rTU7Gg2VyaSvG
103
+ LdbTqbnY/dePfxmLh6TV/rvU9OLX8vvPX+of6XE+/mX9j3P6ekjue+BdYRw3
104
+ MJYmNzdWrzS3e/RN1+N4ulwZkwlHWhxu8JDcPw4PjcVsbi5WY3PbTjZtjr4x
105
+ x5g0c7WeW4bjNpjN8NtGWJ/miZGuF+ajrraqpVpaV3+W65mf+Uq9S1Rz3bNr
106
+ UclDJuXtkPZdF3BKW47jiA21ltvnl46q3iFizxtijvRd2B07UgLF2ZUgcyXm
107
+ SpFxJcxcyZ8rOc+39XI2+XFp9PgvLg/a2ZwgC7D+1CE2/nnl6+6SZQDTobmb
108
+ MWoT47e5uaS/z83HN/hj+UYshfT37dp2y8bEGJpv5nS1YxGf3RQWs/XcZUp7
109
+ Y8EF84FN3y3i7uPpr+xsPV09gofk3t8PvLZWvVPSSvXaoXe33Y357zXJ1h1x
110
+ cKub7d8PNXaN9aFhtttUjcVf5uqxXvuZU6vpWu4h+fGL+8YKiRG2ztlXY/qL
111
+ KGIsZ9PvHvup6GxiPlbTGvG1Le2tHw+1sN8qMSsS7mCjVJAh0oppWRK+mm/u
112
+ 2BsG3oh+5mJfBNgfk5jBRNBgsvWWerq5oGcIu6ibxVpRA5TmsrQCEK21uGTu
113
+ hrnabEGGbfxfc7S5qThbL5bWuz50abu5M8N1zMWSWNMj+AHlzay3+W377qpt
114
+ 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
250
+ 9yph3/P4t/8HTfYMqCsnAgA=
251
+
252
+ http_version: "1.1"