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 +4 -4
- data/.travis.yml +7 -0
- data/Gemfile +7 -6
- data/Gemfile.lock +9 -5
- data/{README.rdoc → README.md} +6 -3
- data/VERSION +1 -1
- data/contained_mr.gemspec +37 -17
- data/lib/contained_mr/job.rb +32 -122
- data/lib/contained_mr/job_logic.rb +126 -0
- data/lib/contained_mr/mock/job.rb +111 -0
- data/lib/contained_mr/mock/runner.rb +86 -0
- data/lib/contained_mr/mock/template.rb +62 -0
- data/lib/contained_mr/mock.rb +3 -0
- data/lib/contained_mr/namespace.rb +24 -0
- data/lib/contained_mr/runner.rb +31 -24
- data/lib/contained_mr/runner_logic.rb +38 -0
- data/lib/contained_mr/template.rb +12 -71
- data/lib/contained_mr/template_logic.rb +91 -0
- data/lib/contained_mr.rb +10 -3
- data/test/concerns/job_state_cases.rb +75 -0
- data/test/helper.rb +1 -0
- data/test/test_cleaner.rb +6 -6
- data/test/test_contained_mr.rb +15 -0
- data/test/test_job.rb +36 -47
- data/test/test_job_logic.rb +52 -0
- data/test/test_mock_job.rb +94 -0
- data/test/test_mock_runner.rb +99 -0
- data/test/test_mock_template.rb +39 -0
- data/test/test_runner.rb +21 -6
- data/test/test_runner_logic.rb +51 -0
- data/test/test_template.rb +15 -28
- data/test/test_template_logic.rb +47 -0
- metadata +52 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dd43659d3e4a97eb0c431a28fd0e18ef0df524e
|
4
|
+
data.tar.gz: c460e444b84ebd56df7295147150e1b49fa42649
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a91d0f3867c8ceda6e96d134bacffaf22f50c6332077b177060cd5a6b03d77e22ce7e81af7770ed18e989e6358324cf54e8cb2bdb0124bd3ea6567994cac8144
|
7
|
+
data.tar.gz: 07322eb0b6961b86c92ce5b3798764847695479473d043167160cb5c9ca07783f11a9dee2cc33eb96d872719abe042225edbb5cbe16cada66623c93837861a34
|
data/.travis.yml
ADDED
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
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
14
|
-
gem
|
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.
|
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
|
-
|
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
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,8 +1,11 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
|
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
|
+
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.
|
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.
|
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-
|
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.
|
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.
|
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<
|
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<
|
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<
|
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
|
|
data/lib/contained_mr/job.rb
CHANGED
@@ -7,16 +7,12 @@ require 'docker'
|
|
7
7
|
|
8
8
|
# A map-reduce job.
|
9
9
|
class ContainedMr::Job
|
10
|
-
|
10
|
+
include ContainedMr::JobLogic
|
11
11
|
|
12
|
-
#
|
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 |
|
39
|
-
|
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
|
-
|
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
|
-
|
227
|
-
|
228
|
-
|
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
|