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
@@ -0,0 +1,86 @@
|
|
1
|
+
# @see {ContainedMr::Runner}
|
2
|
+
class ContainedMr::Mock::Runner
|
3
|
+
# @return {Hash<String, Object>} the options passed to the constructor
|
4
|
+
attr_reader :_container_options
|
5
|
+
# @return {Hash<String, Object>} the time limit passed to the constructor
|
6
|
+
attr_reader :_time_limit
|
7
|
+
# @return {Hash<String, Object>} the output path passed to the constructor
|
8
|
+
attr_reader :_output_path
|
9
|
+
|
10
|
+
# @return {Boolean} true if {#perform} was called
|
11
|
+
def performed?
|
12
|
+
@performed
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return {Boolean} true if {#destroy!} was called
|
16
|
+
def destroyed?
|
17
|
+
@destroyed
|
18
|
+
end
|
19
|
+
|
20
|
+
include ContainedMr::RunnerLogic
|
21
|
+
|
22
|
+
# @see {ContainedMr::Runner#initialize}
|
23
|
+
def initialize(container_options, time_limit, output_path)
|
24
|
+
@_container_options = container_options
|
25
|
+
@_time_limit = time_limit
|
26
|
+
@_output_path = output_path
|
27
|
+
|
28
|
+
@container_id = nil
|
29
|
+
@started_at = @ended_at = nil
|
30
|
+
@status_code = nil
|
31
|
+
@timed_out = nil
|
32
|
+
@stdout = @stderr = nil
|
33
|
+
@output = nil
|
34
|
+
|
35
|
+
@performed = false
|
36
|
+
@destroyed = false
|
37
|
+
end
|
38
|
+
|
39
|
+
# @see {ContainedMr::Runner#perform}
|
40
|
+
def perform
|
41
|
+
@performed = true
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# @see {ContainedMr::Runner#destroy!}
|
46
|
+
def destroy!
|
47
|
+
@destroyed = true
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets the container execution data returned by the mock.
|
52
|
+
#
|
53
|
+
# @param {Hash<Symbol, Object>} attributes values describing the result of
|
54
|
+
# running the job
|
55
|
+
# @return {ContainedMr::Runner} self
|
56
|
+
def _mock_set(attributes)
|
57
|
+
@started_at = attributes[:started_at]
|
58
|
+
@ended_at = attributes[:ended_at]
|
59
|
+
@status_code = attributes[:status_code]
|
60
|
+
@timed_out = attributes[:timed_out]
|
61
|
+
@stdout = attributes[:stdout]
|
62
|
+
@stderr = attributes[:stderr]
|
63
|
+
@output = attributes[:output]
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# Convenience method for looking up an ulimit in the container options.
|
68
|
+
#
|
69
|
+
# @param {String} name the ulimit's name, such as 'cpu' or 'rss'
|
70
|
+
# @return {Number} the ulimit's hard and soft value, or nil if the ulimit was
|
71
|
+
# not found
|
72
|
+
# @raise {RuntimeError} if the ulimit's hard and soft values don't match
|
73
|
+
def _ulimit(name)
|
74
|
+
return nil unless ulimits = @_container_options['Ulimits']
|
75
|
+
|
76
|
+
ulimits.each do |ulimit|
|
77
|
+
if ulimit['Name'] == name
|
78
|
+
if ulimit['Hard'] != ulimit['Soft']
|
79
|
+
raise RuntimeError, "Hard/soft ulimit mismatch for #{name}"
|
80
|
+
end
|
81
|
+
return ulimit['Hard']
|
82
|
+
end
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# @see {ContainedMr::Template}
|
2
|
+
class ContainedMr::Mock::Template
|
3
|
+
# @return {Hash<String, Object>} YAML-parsed mapreduced.yml
|
4
|
+
attr_reader :_definition
|
5
|
+
|
6
|
+
# @return {Hash<String, Symbol|String>} maps file names in the template .zip
|
7
|
+
# to their contents, and maps directory entries to the :directory symbol
|
8
|
+
attr_reader :_zip_contents
|
9
|
+
|
10
|
+
include ContainedMr::TemplateLogic
|
11
|
+
|
12
|
+
# @return {Boolean} true if {#destroy!} was called
|
13
|
+
def destroyed?
|
14
|
+
@destroyed
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see {ContainedMr::Template#initialize}
|
18
|
+
def initialize(name_prefix, id, zip_io)
|
19
|
+
@name_prefix = name_prefix
|
20
|
+
@id = id
|
21
|
+
@image_id = 'mock-template-image-id'
|
22
|
+
@item_count = nil
|
23
|
+
@_definition = nil
|
24
|
+
|
25
|
+
@destroyed = false
|
26
|
+
@_zip_contents = {}
|
27
|
+
|
28
|
+
process_zip zip_io
|
29
|
+
end
|
30
|
+
|
31
|
+
# @see {ContainedMr::Template#destroy!}
|
32
|
+
def destroy!
|
33
|
+
@destroyed = true
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# @see {ContainedMr::Template#new_job}
|
38
|
+
def job_class
|
39
|
+
ContainedMr::Mock::Job
|
40
|
+
end
|
41
|
+
|
42
|
+
# Reads the template .zip and parses the definition.
|
43
|
+
def process_zip(zip_io)
|
44
|
+
# TODO(pwnall): zip_io.read -> zip_io after rubyzip releases 1.1.8
|
45
|
+
Zip::File.open_buffer zip_io.read do |zip|
|
46
|
+
zip.each do |zip_entry|
|
47
|
+
file_name = zip_entry.name
|
48
|
+
if zip_entry.directory?
|
49
|
+
@_zip_contents[file_name] = :directory
|
50
|
+
elsif zip_entry.file?
|
51
|
+
if file_name == 'mapreduced.yml'
|
52
|
+
read_definition zip_entry.get_input_stream
|
53
|
+
next
|
54
|
+
end
|
55
|
+
@_zip_contents[file_name] = zip_entry.get_input_stream.read
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@_zip_contents.freeze
|
60
|
+
end
|
61
|
+
private :process_zip
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Namespace and factory for templates.
|
2
|
+
module ContainedMr
|
3
|
+
# Sets up the template and builds its Docker base image.
|
4
|
+
#
|
5
|
+
# This method should be used instead of calling {ContainedMr::Template.new}
|
6
|
+
# directly. This way, tests can stub {ContainedMr.template_class} to have it
|
7
|
+
# return {ContainedMr::Mock::Template}.
|
8
|
+
#
|
9
|
+
# @param {String} name_prefix prepended to Docker objects, for identification
|
10
|
+
# purposes
|
11
|
+
# @param {String} id the template's unique identifier
|
12
|
+
# @param {String} zip_io IO implementation that produces the template .zip
|
13
|
+
def self.new_template(name_prefix, id, zip_io)
|
14
|
+
template_class.new name_prefix, id, zip_io
|
15
|
+
end
|
16
|
+
|
17
|
+
# The class instantiated by {ContainedMr.new_template}.
|
18
|
+
#
|
19
|
+
# @return {Class} by default {ContainedMr::Template}; tests should stub this
|
20
|
+
# method and have it return {ContainedMr::Mock::Template}
|
21
|
+
def self.template_class
|
22
|
+
ContainedMr::Template
|
23
|
+
end
|
24
|
+
end
|
data/lib/contained_mr/runner.rb
CHANGED
@@ -5,15 +5,20 @@ require 'docker'
|
|
5
5
|
|
6
6
|
# Handles running a single mapper or reducer.
|
7
7
|
class ContainedMr::Runner
|
8
|
-
|
9
|
-
attr_reader :started_at, :ended_at, :status_code, :timed_out
|
10
|
-
attr_reader :stdout, :stderr, :output
|
8
|
+
include ContainedMr::RunnerLogic
|
11
9
|
|
12
|
-
#
|
10
|
+
# Initialize a runner.
|
11
|
+
#
|
12
|
+
# @param {Hash<String, Object>} container_options docker container creation
|
13
|
+
# options, passed to {Docker::Container.create} without modification
|
14
|
+
# @param {Number} time_limit maximum number of seconds that the runner's
|
15
|
+
# container is allowed to execute before being terminated
|
16
|
+
# @param {String} output_path the location of the file inside the container
|
17
|
+
# whose output will be saved
|
13
18
|
def initialize(container_options, time_limit, output_path)
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
19
|
+
@_container_options = container_options
|
20
|
+
@_time_limit = time_limit
|
21
|
+
@_output_path = output_path
|
17
22
|
|
18
23
|
@container_id = nil
|
19
24
|
@started_at = @ended_at = nil
|
@@ -23,8 +28,9 @@ class ContainedMr::Runner
|
|
23
28
|
@output = nil
|
24
29
|
end
|
25
30
|
|
26
|
-
|
27
31
|
# Performs a full mapper / reducer step.
|
32
|
+
#
|
33
|
+
# @return {ContainedMr::Runner} self
|
28
34
|
def perform
|
29
35
|
container = create
|
30
36
|
@container_id = container.id
|
@@ -32,20 +38,29 @@ class ContainedMr::Runner
|
|
32
38
|
execute container
|
33
39
|
fetch_console_output container
|
34
40
|
fetch_file_output container
|
35
|
-
destroy container
|
36
|
-
self
|
41
|
+
destroy! container
|
37
42
|
end
|
38
43
|
|
39
|
-
#
|
40
|
-
|
41
|
-
|
44
|
+
# Removes the container used to run a mapper / reducer.
|
45
|
+
#
|
46
|
+
# @param {Docker::Container} container the mapper / reducer's container; if
|
47
|
+
# not supplied, an extra Docker API query is performed to obtain the
|
48
|
+
# container object
|
49
|
+
# @return {ContainedMr::Runner} self
|
50
|
+
def destroy!(container = nil)
|
51
|
+
unless @container_id.nil?
|
52
|
+
container ||= Docker::Container.get @container_id
|
53
|
+
container.delete force: true
|
54
|
+
@container_id = nil
|
55
|
+
end
|
56
|
+
self
|
42
57
|
end
|
43
58
|
|
44
59
|
# Creates a container for running a mapper or reducer.
|
45
60
|
#
|
46
61
|
# @return {Docker::Container} newly created container
|
47
62
|
def create
|
48
|
-
Docker::Container.create @
|
63
|
+
Docker::Container.create @_container_options
|
49
64
|
end
|
50
65
|
private :create
|
51
66
|
|
@@ -56,7 +71,7 @@ class ContainedMr::Runner
|
|
56
71
|
container.start
|
57
72
|
@started_at = Time.now
|
58
73
|
begin
|
59
|
-
wait_status = container.wait @
|
74
|
+
wait_status = container.wait @_time_limit
|
60
75
|
@status_code = wait_status['StatusCode']
|
61
76
|
@timed_out = false
|
62
77
|
rescue Docker::Error::TimeoutError
|
@@ -107,19 +122,11 @@ class ContainedMr::Runner
|
|
107
122
|
# @return {IO} an IO implementation that sources the .tar data
|
108
123
|
def fetch_tar_output(container)
|
109
124
|
tar_buffer = StringIO.new
|
110
|
-
container.copy @
|
125
|
+
container.copy @_output_path do |data|
|
111
126
|
tar_buffer << data
|
112
127
|
end
|
113
128
|
tar_buffer.rewind
|
114
129
|
tar_buffer
|
115
130
|
end
|
116
131
|
private :fetch_tar_output
|
117
|
-
|
118
|
-
# Removes the container used to run a mapper / reducer.
|
119
|
-
#
|
120
|
-
# @param {Docker::Container} container the mapper / reducer's container
|
121
|
-
def destroy(container)
|
122
|
-
container.delete
|
123
|
-
@container_id = nil
|
124
|
-
end
|
125
132
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Logic shared by {ContainedMr::Runner} and {ContainedMr::Mock::Runner}.
|
2
|
+
module ContainedMr::RunnerLogic
|
3
|
+
# @return {Time} the time when the mapper or reducer starts running
|
4
|
+
attr_reader :started_at
|
5
|
+
# @return {Time} the time when the mapper or reducer stops running or is killed
|
6
|
+
attr_reader :ended_at
|
7
|
+
# @return {Number} the time
|
8
|
+
attr_reader :status_code
|
9
|
+
# @return {Boolean} true if the mapper or reducer was terminated due to
|
10
|
+
# running for too long
|
11
|
+
attr_reader :timed_out
|
12
|
+
|
13
|
+
# @return {String} the data written by the mapper or reducer to stdout
|
14
|
+
attr_reader :stdout
|
15
|
+
# @return {String} the data written by the mapper or reducer to stderr
|
16
|
+
attr_reader :stderr
|
17
|
+
# @return {String} the contents of the file
|
18
|
+
attr_reader :output
|
19
|
+
|
20
|
+
# @return {String} the unique ID of the Docker container used to run the
|
21
|
+
# mapper / reducer; this is nil
|
22
|
+
attr_reader :container_id
|
23
|
+
|
24
|
+
# @return {Number} the container's running time, in seconds
|
25
|
+
def ran_for
|
26
|
+
started_at && ended_at && (ended_at - started_at)
|
27
|
+
end
|
28
|
+
|
29
|
+
# The information written to the mapper status files given to the reducer.
|
30
|
+
#
|
31
|
+
# This is saved in files named 1.json, 2.json, ... provided to the reducer.
|
32
|
+
#
|
33
|
+
# @return {Hash<Symbol, Object>} JSON-compatible representation of the
|
34
|
+
# runner's information
|
35
|
+
def json_file
|
36
|
+
{ ran_for: ran_for, exit_code: status_code, timed_out: timed_out }
|
37
|
+
end
|
38
|
+
end
|
@@ -7,20 +7,15 @@ require 'zip'
|
|
7
7
|
|
8
8
|
# A template is used to spawn multiple Map-Reduce jobs.
|
9
9
|
class ContainedMr::Template
|
10
|
-
|
10
|
+
include ContainedMr::TemplateLogic
|
11
11
|
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# @param {String} name_prefix prepended to Docker objects, for identification
|
15
|
-
# purposes
|
16
|
-
# @param {String} id the job's unique identifier
|
17
|
-
# @param {String} zip_io IO implementation that produces the template .zip
|
12
|
+
# @see {ContainedMr.new_template}
|
18
13
|
def initialize(name_prefix, id, zip_io)
|
19
14
|
@name_prefix = name_prefix
|
20
15
|
@id = id
|
21
16
|
@image_id = nil
|
22
|
-
@definition = nil
|
23
17
|
@item_count = nil
|
18
|
+
@_definition = nil
|
24
19
|
|
25
20
|
tar_buffer = StringIO.new
|
26
21
|
process_zip zip_io, tar_buffer
|
@@ -31,6 +26,8 @@ class ContainedMr::Template
|
|
31
26
|
# Tears down the template's state.
|
32
27
|
#
|
33
28
|
# This removes the template's base Docker image.
|
29
|
+
#
|
30
|
+
# @return {ContainedMr::Template} self
|
34
31
|
def destroy!
|
35
32
|
unless @image_id.nil?
|
36
33
|
# HACK(pwnall): Trick docker-api into issuing a DELETE request by tag.
|
@@ -38,63 +35,17 @@ class ContainedMr::Template
|
|
38
35
|
image.remove
|
39
36
|
@image_id = nil
|
40
37
|
end
|
38
|
+
self
|
41
39
|
end
|
42
40
|
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# @return {String} the Dockerfile
|
46
|
-
def mapper_dockerfile
|
47
|
-
job_dockerfile @definition['mapper'] || {}, 'input'
|
48
|
-
end
|
49
|
-
|
50
|
-
# Computes the Dockerfile used to build a job's reducer image.
|
51
|
-
#
|
52
|
-
# @return {String} the Dockerfile
|
53
|
-
def reducer_dockerfile
|
54
|
-
job_dockerfile @definition['reducer'] || {}, '.'
|
55
|
-
end
|
56
|
-
|
57
|
-
# @return {String} tag applied to the template's base Docker image
|
58
|
-
def image_tag
|
59
|
-
"#{@name_prefix}/base.#{@id}"
|
60
|
-
end
|
61
|
-
|
62
|
-
# Computes the environment variables to be set in a mapper container.
|
41
|
+
# The class instantiated by {ContainedMr::TemplateLogic#new_job}.
|
63
42
|
#
|
64
|
-
# @
|
65
|
-
#
|
66
|
-
def
|
67
|
-
|
43
|
+
# @return {Class} by default {ContainedMr::Job}; tests might want to stub
|
44
|
+
# this method and have it return {ContainedMr::Mock::Job}
|
45
|
+
def job_class
|
46
|
+
ContainedMr::Job
|
68
47
|
end
|
69
48
|
|
70
|
-
# Computes the environment variables to be set in the reducer container.
|
71
|
-
#
|
72
|
-
# @return {Array<String>} environment variables to be set in the mapper
|
73
|
-
def reducer_env
|
74
|
-
[ "ITEMS=#{@item_count.to_s}" ]
|
75
|
-
end
|
76
|
-
|
77
|
-
# @return {String} the map output's path in the mapper Docker container
|
78
|
-
def mapper_output_path
|
79
|
-
(@definition['mapper'] || {})['output'] || '/output'
|
80
|
-
end
|
81
|
-
|
82
|
-
# @return {String} the reducer output's path in the reducer Docker container
|
83
|
-
def reducer_output_path
|
84
|
-
(@definition['reducer'] || {})['output'] || '/output'
|
85
|
-
end
|
86
|
-
|
87
|
-
# @private common code from mapper_dockerfile and reducer_dockerfile
|
88
|
-
def job_dockerfile(job_definition, input_source)
|
89
|
-
<<DOCKER_END
|
90
|
-
FROM #{@image_id}
|
91
|
-
COPY #{input_source} #{job_definition['input'] || '/input'}
|
92
|
-
WORKDIR #{job_definition['chdir'] || '/'}
|
93
|
-
ENTRYPOINT #{JSON.dump(job_definition['cmd'] || ['/bin/sh'])}
|
94
|
-
DOCKER_END
|
95
|
-
end
|
96
|
-
private :job_dockerfile
|
97
|
-
|
98
49
|
# Reads the template .zip and parses the definition.
|
99
50
|
#
|
100
51
|
# @param {IO} zip_io IO implementation that produces the .zip file
|
@@ -120,17 +71,7 @@ DOCKER_END
|
|
120
71
|
end
|
121
72
|
end
|
122
73
|
end
|
123
|
-
|
124
|
-
# Reads the template's definition, using data at the given path.
|
125
|
-
#
|
126
|
-
# @param {IO} yaml_io IO implementation that produces the .yaml file
|
127
|
-
# containing the definition
|
128
|
-
def read_definition(yaml_io)
|
129
|
-
@definition = YAML.load yaml_io.read
|
130
|
-
|
131
|
-
@item_count = @definition['items'] || 1
|
132
|
-
end
|
133
|
-
private :read_definition
|
74
|
+
private :process_zip
|
134
75
|
|
135
76
|
# Builds the template's Docker image, using data at the given path.
|
136
77
|
#
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Logic shared by {ContainedMr::Template} and {ContainedMr::Mock::Template}.
|
2
|
+
module ContainedMr::TemplateLogic
|
3
|
+
# @return {String} prepended to Docker objects, for identification purposes
|
4
|
+
attr_reader :name_prefix
|
5
|
+
|
6
|
+
# @return {String} the template's unique identifier
|
7
|
+
attr_reader :id
|
8
|
+
|
9
|
+
# @return {Number} the number of mapper jobs specified by this template
|
10
|
+
attr_reader :item_count
|
11
|
+
|
12
|
+
# @return {String} image_id the unique ID of the Docker image used as a base
|
13
|
+
# for images built by jobs derived from this template
|
14
|
+
attr_reader :image_id
|
15
|
+
|
16
|
+
# Creates a job using this template.
|
17
|
+
#
|
18
|
+
# @param {String} id the job's unique ID
|
19
|
+
# @param {Hash<String, Object>} json_options job options, extracted from JSON
|
20
|
+
# @return {ContainedMr::Job} a newly created job that uses this template
|
21
|
+
def new_job(id, json_options)
|
22
|
+
job_class.new self, id, json_options
|
23
|
+
end
|
24
|
+
|
25
|
+
# Computes the Dockerfile used to build a job's mapper image.
|
26
|
+
#
|
27
|
+
# @return {String} the Dockerfile
|
28
|
+
def mapper_dockerfile
|
29
|
+
job_dockerfile @_definition['mapper'] || {}, 'input'
|
30
|
+
end
|
31
|
+
|
32
|
+
# Computes the Dockerfile used to build a job's reducer image.
|
33
|
+
#
|
34
|
+
# @return {String} the Dockerfile
|
35
|
+
def reducer_dockerfile
|
36
|
+
job_dockerfile @_definition['reducer'] || {}, '.'
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return {String} tag applied to the template's base Docker image
|
40
|
+
def image_tag
|
41
|
+
"#{@name_prefix}/base.#{@id}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Computes the environment variables to be set in a mapper container.
|
45
|
+
#
|
46
|
+
# @param {Number} i the mapper number
|
47
|
+
# @return {Array<String>} environment variables to be set in the mapper
|
48
|
+
def mapper_env(i)
|
49
|
+
[ "ITEM=#{i}", "ITEMS=#{@item_count.to_s}" ]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Computes the environment variables to be set in the reducer container.
|
53
|
+
#
|
54
|
+
# @return {Array<String>} environment variables to be set in the mapper
|
55
|
+
def reducer_env
|
56
|
+
[ "ITEMS=#{@item_count.to_s}" ]
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return {String} the map output's path in the mapper Docker container
|
60
|
+
def mapper_output_path
|
61
|
+
(@_definition['mapper'] || {})['output'] || '/output'
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return {String} the reducer output's path in the reducer Docker container
|
65
|
+
def reducer_output_path
|
66
|
+
(@_definition['reducer'] || {})['output'] || '/output'
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private common code from mapper_dockerfile and reducer_dockerfile
|
70
|
+
def job_dockerfile(job_definition, input_source)
|
71
|
+
<<DOCKER_END
|
72
|
+
FROM #{@image_id}
|
73
|
+
COPY #{input_source} #{job_definition['input'] || '/input'}
|
74
|
+
WORKDIR #{job_definition['chdir'] || '/'}
|
75
|
+
ENTRYPOINT #{JSON.dump(job_definition['cmd'] || ['/bin/sh'])}
|
76
|
+
DOCKER_END
|
77
|
+
end
|
78
|
+
private :job_dockerfile
|
79
|
+
|
80
|
+
# Reads the template's definition, using data at the given path.
|
81
|
+
#
|
82
|
+
# @param {IO} yaml_io IO implementation that produces the .yaml file
|
83
|
+
# containing the definition
|
84
|
+
def read_definition(yaml_io)
|
85
|
+
@_definition = YAML.load yaml_io.read
|
86
|
+
@_definition.freeze
|
87
|
+
|
88
|
+
@item_count = @_definition['items'] || 1
|
89
|
+
end
|
90
|
+
private :read_definition
|
91
|
+
end
|
data/lib/contained_mr.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require_relative 'contained_mr/namespace.rb'
|
2
|
+
|
3
|
+
require_relative 'contained_mr/job_logic.rb'
|
4
|
+
require_relative 'contained_mr/runner_logic.rb'
|
5
|
+
require_relative 'contained_mr/template_logic.rb'
|
4
6
|
|
5
7
|
require_relative 'contained_mr/cleaner.rb'
|
6
8
|
require_relative 'contained_mr/job.rb'
|
7
9
|
require_relative 'contained_mr/runner.rb'
|
8
10
|
require_relative 'contained_mr/template.rb'
|
11
|
+
|
12
|
+
require_relative 'contained_mr/mock.rb'
|
13
|
+
require_relative 'contained_mr/mock/job.rb'
|
14
|
+
require_relative 'contained_mr/mock/runner.rb'
|
15
|
+
require_relative 'contained_mr/mock/template.rb'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module JobStateCases
|
2
|
+
def test_build_mapper_image_twice
|
3
|
+
@job.build_mapper_image File.read('testdata/input.hello')
|
4
|
+
begin
|
5
|
+
@job.build_mapper_image File.read('testdata/input.hello')
|
6
|
+
flunk 'No exception thrown'
|
7
|
+
rescue RuntimeError => e
|
8
|
+
assert_instance_of RuntimeError, e
|
9
|
+
assert_equal 'Mapper image already exists', e.message
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_run_mapper_without_image
|
14
|
+
begin
|
15
|
+
@job.run_mapper 1
|
16
|
+
flunk 'No exception thrown'
|
17
|
+
rescue RuntimeError => e
|
18
|
+
assert_instance_of RuntimeError, e
|
19
|
+
assert_equal 'Mapper image does not exist', e.message
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_run_invalid_mapper
|
24
|
+
begin
|
25
|
+
@job.run_mapper 4
|
26
|
+
flunk 'No exception thrown'
|
27
|
+
rescue ArgumentError => e
|
28
|
+
assert_instance_of ArgumentError, e
|
29
|
+
assert_equal 'Invalid mapper number 4', e.message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_invalid_mapper_runner
|
34
|
+
begin
|
35
|
+
@job.mapper_runner 4
|
36
|
+
flunk 'No exception thrown'
|
37
|
+
rescue ArgumentError => e
|
38
|
+
assert_instance_of ArgumentError, e
|
39
|
+
assert_equal 'Invalid mapper number 4', e.message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_build_reducer_image_without_mapper_results
|
44
|
+
begin
|
45
|
+
@job.build_reducer_image
|
46
|
+
flunk 'No exception thrown'
|
47
|
+
rescue RuntimeError => e
|
48
|
+
assert_instance_of RuntimeError, e
|
49
|
+
assert_equal 'Not all mappers ran', e.message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_build_reducer_image_twice
|
54
|
+
@job.build_mapper_image File.read('testdata/input.hello')
|
55
|
+
1.upto(3) { |i| @job.run_mapper i }
|
56
|
+
@job.build_reducer_image
|
57
|
+
begin
|
58
|
+
@job.build_reducer_image
|
59
|
+
flunk 'No exception thrown'
|
60
|
+
rescue RuntimeError => e
|
61
|
+
assert_instance_of RuntimeError, e
|
62
|
+
assert_equal 'Reducer image already exists', e.message
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_run_reducer_without_image
|
67
|
+
begin
|
68
|
+
@job.run_reducer
|
69
|
+
flunk 'No exception thrown'
|
70
|
+
rescue RuntimeError => e
|
71
|
+
assert_instance_of RuntimeError, e
|
72
|
+
assert_equal 'Reducer image does not exist', e.message
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/test/helper.rb
CHANGED
data/test/test_cleaner.rb
CHANGED
@@ -2,10 +2,10 @@ require 'helper'
|
|
2
2
|
|
3
3
|
class TestCleaner < MiniTest::Test
|
4
4
|
def setup
|
5
|
-
@template = ContainedMr
|
5
|
+
@template = ContainedMr.new_template 'contained_mrtests', 'hello',
|
6
6
|
StringIO.new(File.binread('testdata/hello.zip'))
|
7
|
-
@job =
|
8
|
-
|
7
|
+
@job = @template.new_job 'testjob',
|
8
|
+
JSON.load(File.read('testdata/job.hello'))
|
9
9
|
@job.build_mapper_image File.read('testdata/input.hello')
|
10
10
|
|
11
11
|
@cleaner = ContainedMr::Cleaner.new 'contained_mrtests'
|
@@ -22,10 +22,10 @@ class TestCleaner < MiniTest::Test
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def test_destroy_all_with_duplicates
|
25
|
-
template2 = ContainedMr
|
25
|
+
template2 = ContainedMr.new_template 'contained_mrtests', 'hello2',
|
26
26
|
StringIO.new(File.binread('testdata/hello.zip'))
|
27
|
-
job2 =
|
28
|
-
|
27
|
+
job2 = template2.new_job 'testjob2',
|
28
|
+
JSON.load(File.read('testdata/job.hello'))
|
29
29
|
job2.build_mapper_image File.read('testdata/input.hello')
|
30
30
|
@cleaner.destroy_all!
|
31
31
|
assert_raises Docker::Error::NotFoundError do
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestContainedMr < MiniTest::Test
|
4
|
+
def test_template_class
|
5
|
+
assert_equal ContainedMr::Template, ContainedMr.template_class
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_new_template
|
9
|
+
ContainedMr.stubs(:template_class).returns ContainedMr::Mock::Template
|
10
|
+
|
11
|
+
template = ContainedMr.new_template 'contained_mrtests', 'hello',
|
12
|
+
StringIO.new(File.binread('testdata/hello.zip'))
|
13
|
+
assert_instance_of ContainedMr::Mock::Template, template
|
14
|
+
end
|
15
|
+
end
|