wakoopa-elasticity 1.2.3

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.
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"