contained_mr 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63dd405ddd7f6926540118a53fae49b4e68eae85
4
- data.tar.gz: 5f60e7e07994d45710b7c1a11149a36e641e6e97
3
+ metadata.gz: 7dd43659d3e4a97eb0c431a28fd0e18ef0df524e
4
+ data.tar.gz: c460e444b84ebd56df7295147150e1b49fa42649
5
5
  SHA512:
6
- metadata.gz: 786419170b2c4b3d2b35d80f3897ace4503f1757e0fe3682a62fcca65d1e1de46f7c2fe721885dec65ea06c7e79ec62d4a70a352812ce9008215ee8ce735b81c
7
- data.tar.gz: 272b5127d2bb5efec7595086b299fb25458523d37d93828b696e2565ae802d9809224be0fd489e1627c92183439d9fd6ed52de0acc27fb2ac9d7b5bed6a6c943
6
+ metadata.gz: a91d0f3867c8ceda6e96d134bacffaf22f50c6332077b177060cd5a6b03d77e22ce7e81af7770ed18e989e6358324cf54e8cb2bdb0124bd3ea6567994cac8144
7
+ data.tar.gz: 07322eb0b6961b86c92ce5b3798764847695479473d043167160cb5c9ca07783f11a9dee2cc33eb96d872719abe042225edbb5cbe16cada66623c93837861a34
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: required
2
+ language: ruby
3
+ services:
4
+ - docker
5
+ rvm:
6
+ - 2.2.2
7
+ script: rake test
data/Gemfile CHANGED
@@ -6,10 +6,11 @@ gem 'rubyzip', '>= 1.1.7', require: 'zip'
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
  group :development do
9
- gem "minitest", ">= 0"
10
- gem "yard", ">= 0.7"
11
- gem "rdoc", ">= 3.12"
12
- gem "bundler", ">= 1.6.1"
13
- gem "jeweler", ">= 2.0.1"
14
- gem "simplecov", ">= 0"
9
+ gem 'bundler', '>= 1.6.1'
10
+ gem 'jeweler', '>= 2.0.1'
11
+ gem 'minitest', '>= 5.8.0'
12
+ gem 'mocha', '>= 1.1.0'
13
+ gem 'rdoc', '>= 4.2.0'
14
+ gem 'simplecov', '>= 0.10.0'
15
+ gem 'yard', '>= 0.8.7.6'
15
16
  end
data/Gemfile.lock CHANGED
@@ -22,7 +22,7 @@ GEM
22
22
  nokogiri (~> 1.6.6)
23
23
  oauth2
24
24
  hashie (3.4.2)
25
- highline (1.7.3)
25
+ highline (1.7.5)
26
26
  jeweler (2.0.1)
27
27
  builder
28
28
  bundler (>= 1.0)
@@ -34,8 +34,11 @@ GEM
34
34
  rdoc
35
35
  json (1.8.3)
36
36
  jwt (1.5.1)
37
+ metaclass (0.0.4)
37
38
  mini_portile (0.6.2)
38
39
  minitest (5.8.0)
40
+ mocha (1.1.0)
41
+ metaclass (~> 0.0.1)
39
42
  multi_json (1.11.2)
40
43
  multi_xml (0.5.5)
41
44
  multipart-post (2.0.0)
@@ -66,11 +69,12 @@ DEPENDENCIES
66
69
  bundler (>= 1.6.1)
67
70
  docker-api (>= 1.22.4)
68
71
  jeweler (>= 2.0.1)
69
- minitest
70
- rdoc (>= 3.12)
72
+ minitest (>= 5.8.0)
73
+ mocha (>= 1.1.0)
74
+ rdoc (>= 4.2.0)
71
75
  rubyzip (>= 1.1.7)
72
- simplecov
73
- yard (>= 0.7)
76
+ simplecov (>= 0.10.0)
77
+ yard (>= 0.8.7.6)
74
78
 
75
79
  BUNDLED WITH
76
80
  1.10.6
@@ -1,8 +1,11 @@
1
- = contained_mr
1
+ # contained_mr
2
+
3
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/pwnall/contained_mr/master/frames)
4
+ [![Build Status](https://travis-ci.org/pwnall/contained_mr.svg?branch=master)](https://travis-ci.org/pwnall/contained_mr)
2
5
 
3
6
  Map-Reduce where both the mappers and the reducer run inside Docker containers.
4
7
 
5
- == Contributing to contained_mr
8
+ ## Contributing to contained_mr
6
9
 
7
10
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
11
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
@@ -12,6 +15,6 @@ Map-Reduce where both the mappers and the reducer run inside Docker containers.
12
15
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
16
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
17
 
15
- == Copyright
18
+ ## Copyright
16
19
 
17
20
  Copyright (c) 2015 Victor Costan. See LICENSE.txt for further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
data/contained_mr.gemspec CHANGED
@@ -2,41 +2,58 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: contained_mr 0.1.2 ruby lib
5
+ # stub: contained_mr 0.2.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "contained_mr"
9
- s.version = "0.1.2"
9
+ s.version = "0.2.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Victor Costan"]
14
- s.date = "2015-09-03"
14
+ s.date = "2015-09-17"
15
15
  s.description = "Plumbing for running mappers and reducers inside Docker containers"
16
16
  s.email = "victor@costan.us"
17
17
  s.extra_rdoc_files = [
18
18
  "LICENSE",
19
- "README.rdoc"
19
+ "README.md"
20
20
  ]
21
21
  s.files = [
22
22
  ".document",
23
+ ".travis.yml",
23
24
  "Gemfile",
24
25
  "Gemfile.lock",
25
26
  "LICENSE",
26
- "README.rdoc",
27
+ "README.md",
27
28
  "Rakefile",
28
29
  "VERSION",
29
30
  "contained_mr.gemspec",
30
31
  "lib/contained_mr.rb",
31
32
  "lib/contained_mr/cleaner.rb",
32
33
  "lib/contained_mr/job.rb",
34
+ "lib/contained_mr/job_logic.rb",
35
+ "lib/contained_mr/mock.rb",
36
+ "lib/contained_mr/mock/job.rb",
37
+ "lib/contained_mr/mock/runner.rb",
38
+ "lib/contained_mr/mock/template.rb",
39
+ "lib/contained_mr/namespace.rb",
33
40
  "lib/contained_mr/runner.rb",
41
+ "lib/contained_mr/runner_logic.rb",
34
42
  "lib/contained_mr/template.rb",
43
+ "lib/contained_mr/template_logic.rb",
44
+ "test/concerns/job_state_cases.rb",
35
45
  "test/helper.rb",
36
46
  "test/test_cleaner.rb",
47
+ "test/test_contained_mr.rb",
37
48
  "test/test_job.rb",
49
+ "test/test_job_logic.rb",
50
+ "test/test_mock_job.rb",
51
+ "test/test_mock_runner.rb",
52
+ "test/test_mock_template.rb",
38
53
  "test/test_runner.rb",
54
+ "test/test_runner_logic.rb",
39
55
  "test/test_template.rb",
56
+ "test/test_template_logic.rb",
40
57
  "testdata/Dockerfile.hello.mapper",
41
58
  "testdata/Dockerfile.hello.reducer",
42
59
  "testdata/hello/Dockerfile",
@@ -59,31 +76,34 @@ Gem::Specification.new do |s|
59
76
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
77
  s.add_runtime_dependency(%q<docker-api>, [">= 1.22.4"])
61
78
  s.add_runtime_dependency(%q<rubyzip>, [">= 1.1.7"])
62
- s.add_development_dependency(%q<minitest>, [">= 0"])
63
- s.add_development_dependency(%q<yard>, [">= 0.7"])
64
- s.add_development_dependency(%q<rdoc>, [">= 3.12"])
65
79
  s.add_development_dependency(%q<bundler>, [">= 1.6.1"])
66
80
  s.add_development_dependency(%q<jeweler>, [">= 2.0.1"])
67
- s.add_development_dependency(%q<simplecov>, [">= 0"])
81
+ s.add_development_dependency(%q<minitest>, [">= 5.8.0"])
82
+ s.add_development_dependency(%q<mocha>, [">= 1.1.0"])
83
+ s.add_development_dependency(%q<rdoc>, [">= 4.2.0"])
84
+ s.add_development_dependency(%q<simplecov>, [">= 0.10.0"])
85
+ s.add_development_dependency(%q<yard>, [">= 0.8.7.6"])
68
86
  else
69
87
  s.add_dependency(%q<docker-api>, [">= 1.22.4"])
70
88
  s.add_dependency(%q<rubyzip>, [">= 1.1.7"])
71
- s.add_dependency(%q<minitest>, [">= 0"])
72
- s.add_dependency(%q<yard>, [">= 0.7"])
73
- s.add_dependency(%q<rdoc>, [">= 3.12"])
74
89
  s.add_dependency(%q<bundler>, [">= 1.6.1"])
75
90
  s.add_dependency(%q<jeweler>, [">= 2.0.1"])
76
- s.add_dependency(%q<simplecov>, [">= 0"])
91
+ s.add_dependency(%q<minitest>, [">= 5.8.0"])
92
+ s.add_dependency(%q<mocha>, [">= 1.1.0"])
93
+ s.add_dependency(%q<rdoc>, [">= 4.2.0"])
94
+ s.add_dependency(%q<simplecov>, [">= 0.10.0"])
95
+ s.add_dependency(%q<yard>, [">= 0.8.7.6"])
77
96
  end
78
97
  else
79
98
  s.add_dependency(%q<docker-api>, [">= 1.22.4"])
80
99
  s.add_dependency(%q<rubyzip>, [">= 1.1.7"])
81
- s.add_dependency(%q<minitest>, [">= 0"])
82
- s.add_dependency(%q<yard>, [">= 0.7"])
83
- s.add_dependency(%q<rdoc>, [">= 3.12"])
84
100
  s.add_dependency(%q<bundler>, [">= 1.6.1"])
85
101
  s.add_dependency(%q<jeweler>, [">= 2.0.1"])
86
- s.add_dependency(%q<simplecov>, [">= 0"])
102
+ s.add_dependency(%q<minitest>, [">= 5.8.0"])
103
+ s.add_dependency(%q<mocha>, [">= 1.1.0"])
104
+ s.add_dependency(%q<rdoc>, [">= 4.2.0"])
105
+ s.add_dependency(%q<simplecov>, [">= 0.10.0"])
106
+ s.add_dependency(%q<yard>, [">= 0.8.7.6"])
87
107
  end
88
108
  end
89
109
 
@@ -7,16 +7,12 @@ require 'docker'
7
7
 
8
8
  # A map-reduce job.
9
9
  class ContainedMr::Job
10
- attr_reader :id, :item_count, :mapper_image_id, :reducer_image_id
10
+ include ContainedMr::JobLogic
11
11
 
12
- # Sets up the job.
13
- #
14
- # @param {Template} template data used to spawn this job
15
- # @param {String} id the job's unique ID
16
- # @param {Hash<String, Object>} json_options job options, extracted from JSON
12
+ # @see {ContainedMr::TemplateLogic#create_job}
17
13
  def initialize(template, id, json_options)
18
- @id = id
19
14
  @template = template
15
+ @id = id
20
16
  @name_prefix = template.name_prefix
21
17
  @item_count = template.item_count
22
18
 
@@ -34,17 +30,13 @@ class ContainedMr::Job
34
30
  #
35
31
  # This removes the job's containers, as well as the mapper and reducer Docker
36
32
  # images, if they still exist.
33
+ #
34
+ # @return {ContainedMr::Job} self
37
35
  def destroy!
38
- @mappers.each do |runner|
39
- next if runner.nil? or runner.container_id.nil?
40
- container = Docker::Container.get runner.container_id
41
- container.delete force: true
42
- end
43
-
44
- unless @reducer.nil? or @reducer.container_id.nil?
45
- container = Docker::Container.get @reducer.container_id
46
- container.delete force: true
36
+ @mappers.each do |mapper|
37
+ mapper.destroy! unless mapper.nil?
47
38
  end
39
+ @reducer.destroy! unless @reducer.nil?
48
40
 
49
41
  unless @mapper_image_id.nil?
50
42
  # HACK(pwnall): Trick docker-api into issuing a DELETE request by tag.
@@ -59,23 +51,8 @@ class ContainedMr::Job
59
51
  image.remove
60
52
  @reducer_image_id = nil
61
53
  end
62
- end
63
54
 
64
- # Returns the runner used for a mapper.
65
- #
66
- # @param {Number} i the mapper number
67
- # @return {ContainedMr::Runner} the runner used for the given mapper; nil if
68
- # the given mapper was not started
69
- def mapper_runner(i)
70
- @mappers[i - 1]
71
- end
72
-
73
- # Returns the runner used for the reducer.
74
- #
75
- # @return {ContainedMr::Runner} the runner used for reducer; nil if the
76
- # reducer was not started
77
- def reducer_runner
78
- @reducer
55
+ self
79
56
  end
80
57
 
81
58
  # Builds the Docker image used to run this job's mappers.
@@ -83,6 +60,10 @@ class ContainedMr::Job
83
60
  # @param {String} mapper_input data passed to the mappers
84
61
  # @return {String} the newly built Docker image's ID
85
62
  def build_mapper_image(mapper_input)
63
+ unless @mapper_image_id.nil?
64
+ raise RuntimeError, 'Mapper image already exists'
65
+ end
66
+
86
67
  tar_io = mapper_tar_context mapper_input
87
68
  image = Docker::Image.build_from_tar tar_io, t: mapper_image_tag
88
69
  @mapper_image_id = image.id
@@ -92,6 +73,13 @@ class ContainedMr::Job
92
73
  #
93
74
  # @return {String} the newly built Docker image's ID
94
75
  def build_reducer_image
76
+ unless @reducer_image_id.nil?
77
+ raise RuntimeError, 'Reducer image already exists'
78
+ end
79
+ 1.upto @item_count do |i|
80
+ raise RuntimeError, 'Not all mappers ran' if mapper_runner(i).nil?
81
+ end
82
+
95
83
  tar_io = reducer_tar_context
96
84
  image = Docker::Image.build_from_tar tar_io, t: reducer_image_tag
97
85
  @reducer_image_id = image.id
@@ -102,6 +90,11 @@ class ContainedMr::Job
102
90
  # @param {Number} i the mapper to run
103
91
  # @return {ContainedMr::Runner} the runner used by the mapper
104
92
  def run_mapper(i)
93
+ if i < 1 || i > @item_count
94
+ raise ArgumentError, "Invalid mapper number #{i}"
95
+ end
96
+ raise RuntimeError, 'Mapper image does not exist' if @mapper_image_id.nil?
97
+
105
98
  mapper = ContainedMr::Runner.new mapper_container_options(i),
106
99
  @mapper_options[:wait_time], @template.mapper_output_path
107
100
  @mappers[i - 1] = mapper
@@ -112,96 +105,16 @@ class ContainedMr::Job
112
105
  #
113
106
  # @return {ContainedMr::Runner} the runner used by the reducer
114
107
  def run_reducer
108
+ if @reducer_image_id.nil?
109
+ raise RuntimeError, 'Reducer image does not exist'
110
+ end
111
+
115
112
  reducer = ContainedMr::Runner.new reducer_container_options,
116
113
  @reducer_options[:wait_time], @template.reducer_output_path
117
114
  @reducer = reducer
118
115
  @reducer.perform
119
116
  end
120
117
 
121
- # @return {String} tag applied to the Docker image used by the job's mappers
122
- def mapper_image_tag
123
- "#{@name_prefix}/mapper.#{@id}"
124
- end
125
-
126
- # @return {String} tag applied to the Docker image used by the job's reducers
127
- def reducer_image_tag
128
- "#{@name_prefix}/reducer.#{@id}"
129
- end
130
-
131
- # @return {Hash<String, Object>} params used to create a mapper container
132
- def mapper_container_options(i)
133
- ulimits = @mapper_options[:ulimits].map do |k, v|
134
- { "Name" => k.to_s, "Soft" => v, "Hard" => v }
135
- end
136
-
137
- {
138
- 'name' => "#{@name_prefix}_mapper.#{@id}.#{i}",
139
- 'Image' => @mapper_image_id,
140
- 'Hostname' => "#{i}.mapper", 'Domainname' => '',
141
- 'Labels' => { 'contained_mr.ctl' => @name_prefix },
142
- 'Env' => @template.mapper_env(i), 'Ulimits' => ulimits,
143
- 'NetworkDisabled' => true, 'ExposedPorts' => {},
144
- }
145
- end
146
-
147
- # @return {Hash<String, Object>} params used to create a reducer container
148
- def reducer_container_options
149
- ulimits = @reducer_options[:ulimits].map do |k, v|
150
- { "Name" => k.to_s, "Soft" => v, "Hard" => v }
151
- end
152
-
153
- {
154
- 'name' => "#{@name_prefix}_reducer.#{@id}",
155
- 'Image' => @reducer_image_id,
156
- 'Hostname' => 'reducer', 'Domainname' => '',
157
- 'Labels' => { 'contained_mr.ctl' => @name_prefix },
158
- 'Env' => @template.reducer_env, 'Ulimits' => ulimits,
159
- 'NetworkDisabled' => true, 'ExposedPorts' => {},
160
- }
161
- end
162
-
163
- # Reads in JSON options and sets defaults.
164
- def parse_options(json_options)
165
- mapper = json_options['mapper'] || {}
166
- mapper_ulimits = mapper['ulimits'] || {}
167
- @mapper_options = {
168
- wait_time: mapper['wait_time'] || 60,
169
- ulimits: {
170
- cpu: mapper_ulimits['cpu'] || 60, # seconds
171
- rss: mapper_ulimits['rss'] || 500_000, # pages
172
- }
173
- }
174
-
175
- reducer = json_options['reducer'] || {}
176
- reducer_ulimits = reducer['ulimits'] || {}
177
- @reducer_options = {
178
- wait_time: reducer['wait_time'] || 60,
179
- ulimits: {
180
- cpu: reducer_ulimits['cpu'] || 60,
181
- rss: reducer_ulimits['rss'] || 500_000,
182
- }
183
- }
184
- end
185
- private :parse_options
186
-
187
- # Builds the .tar context used to create the mapper's Docker image.
188
- #
189
- # @param {String} mapper_input data passed to the mappers
190
- # @return {IO} an IO implementation that sources the .tar data
191
- def mapper_tar_context(mapper_input)
192
- tar_buffer = StringIO.new
193
- Gem::Package::TarWriter.new tar_buffer do |tar|
194
- tar.add_file 'Dockerfile', 0644 do |docker_io|
195
- docker_io.write @template.mapper_dockerfile
196
- end
197
- tar.add_file 'input', 0644 do |input_io|
198
- input_io.write mapper_input
199
- end
200
- end
201
- tar_buffer.rewind
202
- tar_buffer
203
- end
204
- private :mapper_tar_context
205
118
 
206
119
  # Builds the .tar context used to create the mapper's Docker image.
207
120
  #
@@ -223,12 +136,9 @@ class ContainedMr::Job
223
136
  tar.add_file("#{i}.stdout", 0644) { |io| io.write mapper.stdout }
224
137
  tar.add_file("#{i}.stderr", 0644) { |io| io.write mapper.stderr }
225
138
 
226
- status = {
227
- ran_for: mapper.ran_for,
228
- exit_code: mapper.status_code,
229
- timed_out: mapper.timed_out,
230
- }
231
- tar.add_file("#{i}.json", 0644) { |io| io.write status.to_json }
139
+ tar.add_file("#{i}.json", 0644) do |io|
140
+ io.write mapper.json_file.to_json
141
+ end
232
142
  end
233
143
  end
234
144
  tar_buffer.rewind
@@ -0,0 +1,126 @@
1
+ # Logic shared by {ContainedMr::Job} and {ContainedMr::Mock::Job}.
2
+ module ContainedMr::JobLogic
3
+ # @return {ContainedMr::Template} the template this job is derived from
4
+ attr_reader :template
5
+
6
+ # @return {String} prepended to Docker objects, for identification purposes
7
+ attr_reader :name_prefix
8
+
9
+ # @return {String} the job's unique identifier
10
+ attr_reader :id
11
+
12
+ # @return {Number} the number of mapper jobs that will be run
13
+ attr_reader :item_count
14
+
15
+ # @return {String} the unique ID of the Docker image used to run the mappers
16
+ attr_reader :mapper_image_id
17
+
18
+ # @return {String} the unique ID of the Docker image used to run the reducer
19
+ attr_reader :reducer_image_id
20
+
21
+ # Returns the runner used for a mapper.
22
+ #
23
+ # @param {Number} i the mapper number
24
+ # @return {ContainedMr::Runner} the runner used for the given mapper; nil if
25
+ # the given mapper was not started
26
+ def mapper_runner(i)
27
+ if i < 1 || i > @item_count
28
+ raise ArgumentError, "Invalid mapper number #{i}"
29
+ end
30
+ @mappers[i - 1]
31
+ end
32
+
33
+ # Returns the runner used for the reducer.
34
+ #
35
+ # @return {ContainedMr::Runner} the runner used for reducer; nil if the
36
+ # reducer was not started
37
+ def reducer_runner
38
+ @reducer
39
+ end
40
+
41
+
42
+ # @return {String} tag applied to the Docker image used by the job's mappers
43
+ def mapper_image_tag
44
+ "#{@name_prefix}/mapper.#{@id}"
45
+ end
46
+
47
+ # @return {String} tag applied to the Docker image used by the job's reducers
48
+ def reducer_image_tag
49
+ "#{@name_prefix}/reducer.#{@id}"
50
+ end
51
+
52
+ # @return {Hash<String, Object>} params used to create a mapper container
53
+ def mapper_container_options(i)
54
+ ulimits = @mapper_options[:ulimits].map do |k, v|
55
+ { "Name" => k.to_s, "Soft" => v, "Hard" => v }
56
+ end
57
+
58
+ {
59
+ 'name' => "#{@name_prefix}_mapper.#{@id}.#{i}",
60
+ 'Image' => @mapper_image_id,
61
+ 'Hostname' => "#{i}.mapper", 'Domainname' => '',
62
+ 'Labels' => { 'contained_mr.ctl' => @name_prefix },
63
+ 'Env' => @template.mapper_env(i), 'Ulimits' => ulimits,
64
+ 'NetworkDisabled' => true, 'ExposedPorts' => {},
65
+ }
66
+ end
67
+
68
+ # @return {Hash<String, Object>} params used to create a reducer container
69
+ def reducer_container_options
70
+ ulimits = @reducer_options[:ulimits].map do |k, v|
71
+ { "Name" => k.to_s, "Soft" => v, "Hard" => v }
72
+ end
73
+
74
+ {
75
+ 'name' => "#{@name_prefix}_reducer.#{@id}",
76
+ 'Image' => @reducer_image_id,
77
+ 'Hostname' => 'reducer', 'Domainname' => '',
78
+ 'Labels' => { 'contained_mr.ctl' => @name_prefix },
79
+ 'Env' => @template.reducer_env, 'Ulimits' => ulimits,
80
+ 'NetworkDisabled' => true, 'ExposedPorts' => {},
81
+ }
82
+ end
83
+
84
+ # Reads in JSON options and sets defaults.
85
+ def parse_options(json_options)
86
+ mapper = json_options['mapper'] || {}
87
+ mapper_ulimits = mapper['ulimits'] || {}
88
+ @mapper_options = {
89
+ wait_time: mapper['wait_time'] || 60,
90
+ ulimits: {
91
+ cpu: mapper_ulimits['cpu'] || 60, # seconds
92
+ rss: mapper_ulimits['rss'] || 500_000, # pages
93
+ }
94
+ }
95
+
96
+ reducer = json_options['reducer'] || {}
97
+ reducer_ulimits = reducer['ulimits'] || {}
98
+ @reducer_options = {
99
+ wait_time: reducer['wait_time'] || 60,
100
+ ulimits: {
101
+ cpu: reducer_ulimits['cpu'] || 60,
102
+ rss: reducer_ulimits['rss'] || 500_000,
103
+ }
104
+ }
105
+ end
106
+ private :parse_options
107
+
108
+ # Builds the .tar context used to create the mapper's Docker image.
109
+ #
110
+ # @param {String} mapper_input data passed to the mappers
111
+ # @return {IO} an IO implementation that sources the .tar data
112
+ def mapper_tar_context(mapper_input)
113
+ tar_buffer = StringIO.new
114
+ Gem::Package::TarWriter.new tar_buffer do |tar|
115
+ tar.add_file 'Dockerfile', 0644 do |docker_io|
116
+ docker_io.write @template.mapper_dockerfile
117
+ end
118
+ tar.add_file 'input', 0644 do |input_io|
119
+ input_io.write mapper_input
120
+ end
121
+ end
122
+ tar_buffer.rewind
123
+ tar_buffer
124
+ end
125
+ private :mapper_tar_context
126
+ end
@@ -0,0 +1,111 @@
1
+ # @see {ContainedMr::Job}
2
+ class ContainedMr::Mock::Job
3
+ # @see {ContainedMr::Job}
4
+ attr_reader :template, :id, :name_prefix, :item_count
5
+
6
+ # @return {Hash} the options provided to the Job constructor
7
+ attr_reader :_json_options
8
+
9
+ # @return {String} the input data provided to {#build_mapper_image}
10
+ attr_reader :_mapper_input
11
+
12
+ include ContainedMr::JobLogic
13
+
14
+ # @return {Boolean} true if {#destroy!} was called
15
+ def destroyed?
16
+ @destroyed
17
+ end
18
+
19
+ # @see {ContainedMr::Job#initialize}
20
+ def initialize(template, id, json_options)
21
+ @template = template
22
+ @id = id
23
+ @name_prefix = template.name_prefix
24
+ @item_count = template.item_count
25
+
26
+ @mapper_image_id = nil
27
+ @reducer_image_id = nil
28
+
29
+ @mappers = Array.new @item_count
30
+ @reducer = nil
31
+ @mapper_options = nil
32
+ @reducer_options = nil
33
+ @_mapper_input = nil
34
+
35
+ @destroyed = false
36
+ @_json_options = json_options
37
+ parse_options json_options
38
+
39
+ @mock_mappers = (1..@item_count).map do |i|
40
+ ContainedMr::Mock::Runner.new mapper_container_options(i),
41
+ @mapper_options[:wait_time], @template.mapper_output_path
42
+ end
43
+ @mock_reducer = ContainedMr::Mock::Runner.new reducer_container_options,
44
+ @reducer_options[:wait_time], @template.reducer_output_path
45
+ end
46
+
47
+ # @see {ContainedMr::Job#destroy}
48
+ def destroy!
49
+ @destroyed = true
50
+ self
51
+ end
52
+
53
+ # @see {ContainedMr::Job#build_mapper_image}
54
+ def build_mapper_image(mapper_input)
55
+ unless @mapper_image_id.nil?
56
+ raise RuntimeError, 'Mapper image already exists'
57
+ end
58
+ @_mapper_input = mapper_input
59
+ @mapper_image_id = 'mock-job-mapper-image-id'
60
+ end
61
+
62
+ # @see {ContainedMr::Job#build_reducer_image}
63
+ def build_reducer_image
64
+ unless @reducer_image_id.nil?
65
+ raise RuntimeError, 'Reducer image already exists'
66
+ end
67
+ 1.upto @item_count do |i|
68
+ raise RuntimeError, 'Not all mappers ran' if mapper_runner(i).nil?
69
+ end
70
+ @reducer_image_id = 'mock-job-reducer-image-id'
71
+ end
72
+
73
+ # @see {ContainedMr::Job#run_mapper}
74
+ def run_mapper(i)
75
+ if i < 1 || i > @item_count
76
+ raise ArgumentError, "Invalid mapper number #{i}"
77
+ end
78
+ raise RuntimeError, 'Mapper image does not exist' if @mapper_image_id.nil?
79
+ @mappers[i - 1] = @mock_mappers[i - 1]
80
+ end
81
+
82
+ # @see {ContainedMr::Job#run_reducer}
83
+ def run_reducer
84
+ if @reducer_image_id.nil?
85
+ raise RuntimeError, 'Reducer image does not exist'
86
+ end
87
+ @reducer = @mock_reducer
88
+ end
89
+
90
+ # Returns the mock pretending to be the runner used for a mapper.
91
+ #
92
+ # @param {Number} i the mapper number
93
+ # @return {ContainedMr::Mock::Runner} the runner that will be returned
94
+ # by {ContainedMr::Mock::Job#mapper_runner} after
95
+ # {ContainedMr::Mock::Job#run_mapper} completes.
96
+ def _mock_mapper_runner(i)
97
+ if i < 1 || i > @item_count
98
+ raise ArgumentError, "Invalid mapper number #{i}"
99
+ end
100
+ @mock_mappers[i - 1]
101
+ end
102
+
103
+ # Returns the mock pretending to be the runner used for the reducer.
104
+ #
105
+ # @return {ContainedMr::Mock::Runner} the mock runner that will be returned
106
+ # by {ContainedMr::Mock::Job#reducer_runner} after
107
+ # {ContainedMr::Mock::Job#run_reducer} completes.
108
+ def _mock_reducer_runner
109
+ @mock_reducer
110
+ end
111
+ end