ripe 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -5
- data/Guardfile +2 -4
- data/README.md +1 -0
- data/bin/ripe +1 -59
- data/lib/ripe.rb +8 -1
- data/lib/ripe/blocks.rb +13 -0
- data/lib/ripe/blocks/block.rb +142 -0
- data/lib/ripe/blocks/liquid_block.rb +48 -0
- data/lib/ripe/blocks/multi_block.rb +71 -0
- data/lib/ripe/blocks/parallel_block.rb +29 -0
- data/lib/ripe/blocks/serial_block.rb +61 -0
- data/lib/ripe/blocks/working_block.rb +101 -0
- data/lib/ripe/cli.rb +121 -0
- data/lib/ripe/cli/helper.rb +31 -0
- data/lib/ripe/db.rb +7 -0
- data/lib/ripe/db/task.rb +42 -0
- data/lib/ripe/db/task_migration.rb +33 -0
- data/lib/ripe/db/worker.rb +64 -0
- data/lib/ripe/db/worker_migration.rb +41 -0
- data/lib/ripe/dsl.rb +2 -4
- data/lib/ripe/dsl/task_dsl.rb +4 -2
- data/lib/ripe/dsl/workflow_dsl.rb +5 -0
- data/lib/ripe/library.rb +34 -45
- data/lib/ripe/repo.rb +24 -23
- data/lib/ripe/version.rb +1 -1
- data/lib/ripe/worker_controller.rb +72 -144
- data/lib/ripe/worker_controller/preparer.rb +172 -0
- data/lib/ripe/worker_controller/syncer.rb +118 -0
- data/spec/cli_spec.rb +14 -0
- data/spec/library_spec.rb +18 -18
- data/spec/spec_helper.rb +2 -0
- data/spec/testpack.rb +16 -5
- data/spec/testpack/.ripe/meta.db +0 -0
- data/spec/testpack/.ripe/tasks/bar.sh +3 -0
- data/spec/testpack/{ripe → .ripe}/tasks/foo.sh +0 -0
- data/spec/testpack/.ripe/workers/1/1.sh +16 -0
- data/spec/testpack/.ripe/workers/1/2.sh +16 -0
- data/spec/testpack/.ripe/workers/1/job.sh +54 -0
- data/spec/testpack/.ripe/workers/2/3.sh +16 -0
- data/spec/testpack/.ripe/workers/2/4.sh +16 -0
- data/spec/testpack/.ripe/workers/2/job.sh +54 -0
- data/spec/testpack/.ripe/workers/3/5.sh +16 -0
- data/spec/testpack/.ripe/workers/3/6.sh +16 -0
- data/spec/testpack/.ripe/workers/3/job.sh +54 -0
- data/spec/testpack/.ripe/workflows/foobar.rb +23 -0
- data/spec/testpack/{case/Sample1 → Sample1}/bar_output.txt +0 -0
- data/spec/testpack/{case/Sample1 → Sample1}/foo_input.txt +0 -0
- data/spec/testpack/{case/Sample1 → Sample1}/foo_output.txt +0 -0
- data/spec/testpack/{case/Sample2 → Sample2}/bar_output.txt +0 -0
- data/spec/testpack/{case/Sample2 → Sample2}/foo_input.txt +0 -0
- data/spec/testpack/{case/Sample2 → Sample2}/foo_output.txt +0 -0
- data/spec/testpack/{case/Sample3 → Sample3}/bar_output.txt +0 -0
- data/spec/testpack/{case/Sample3 → Sample3}/foo_input.txt +0 -0
- data/spec/testpack/{case/Sample3 → Sample3}/foo_output.txt +0 -0
- data/spec/worker_controller_spec.rb +143 -0
- metadata +66 -40
- data/lib/ripe/block.rb +0 -41
- data/lib/ripe/liquid_block.rb +0 -17
- data/lib/ripe/multi_block.rb +0 -35
- data/lib/ripe/parallel_block.rb +0 -13
- data/lib/ripe/serial_block.rb +0 -37
- data/lib/ripe/task.rb +0 -21
- data/lib/ripe/task_migration.rb +0 -18
- data/lib/ripe/worker.rb +0 -44
- data/lib/ripe/worker_migration.rb +0 -26
- data/lib/ripe/working_block.rb +0 -41
- data/spec/block_spec.rb +0 -7
- data/spec/ripe_spec.rb +0 -7
- data/spec/testpack/ripe/tasks/bar.sh +0 -3
- data/spec/testpack/ripe/workflows/foobar.rb +0 -23
data/lib/ripe/dsl.rb
CHANGED
data/lib/ripe/dsl/task_dsl.rb
CHANGED
@@ -33,16 +33,18 @@ module Ripe
|
|
33
33
|
# @return [WorkingBlock, nil]
|
34
34
|
|
35
35
|
def task(handle, &block)
|
36
|
-
filename =
|
36
|
+
filename = Library.find(:task, handle)
|
37
37
|
abort "Could not find task #{handle}." if filename == nil
|
38
38
|
|
39
39
|
params = TaskDSL.new(handle, &block).params
|
40
|
-
WorkingBlock.new(filename, params)
|
40
|
+
Blocks::WorkingBlock.new(filename, params)
|
41
41
|
end
|
42
42
|
|
43
43
|
##
|
44
44
|
# This class provides a DSL for defining a task. It should only be called
|
45
45
|
# by #task.
|
46
|
+
#
|
47
|
+
# @attr_reader params [Hash<Symbol, String>] list of parameters
|
46
48
|
|
47
49
|
class TaskDSL
|
48
50
|
|
@@ -37,6 +37,11 @@ module Ripe
|
|
37
37
|
##
|
38
38
|
# This class provides a DSL for defining a workflow. It should only be
|
39
39
|
# called by #workflow.
|
40
|
+
#
|
41
|
+
# @attr_reader handle [String] the name of the workflow
|
42
|
+
# @attr_reader params [Hash<Symbol, String>] list of parameters
|
43
|
+
# @attr_reader callback [Proc] the block describing what to do apply to
|
44
|
+
# each sample
|
40
45
|
|
41
46
|
class WorkflowDSL
|
42
47
|
|
data/lib/ripe/library.rb
CHANGED
@@ -1,59 +1,48 @@
|
|
1
1
|
module Ripe
|
2
2
|
|
3
3
|
##
|
4
|
-
# This class represents a library containing all the components
|
5
|
-
# to ripe (tasks and workflows) based on what is contained in the
|
6
|
-
# environment variable.
|
4
|
+
# This singleton class represents a library containing all the components
|
5
|
+
# accessible to ripe (tasks and workflows) based on what is contained in the
|
6
|
+
# +RIPELIB+ environment variable.
|
7
7
|
|
8
|
-
|
8
|
+
module Library
|
9
9
|
|
10
|
-
|
10
|
+
class << self
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
##
|
13
|
+
# Provide a list of search paths
|
14
|
+
#
|
15
|
+
# @return [List] Return the list of search paths
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@paths = "#{Dir.pwd}/#{Repo::REPOSITORY_PATH}:#{ENV['RIPELIB']}".split(/:/)
|
21
|
-
end
|
22
|
-
|
23
|
-
##
|
24
|
-
# Search throughout the library for a task component by the name of
|
25
|
-
# +handle+. When there is more than one match, give precendence to the
|
26
|
-
# component whose path is declared first.
|
27
|
-
#
|
28
|
-
# @param handle [String] Task to search for
|
29
|
-
# @return [String, nil] Return the full path of the component if found,
|
30
|
-
# and +nil+ otherwise.
|
31
|
-
|
32
|
-
def find_task(handle)
|
33
|
-
search = @paths.map do |path|
|
34
|
-
filename = "#{path}/tasks/#{handle}.sh"
|
35
|
-
(File.exists? filename) ? filename : nil
|
17
|
+
def paths
|
18
|
+
# Prepend the working directory to the list of paths so that the
|
19
|
+
# working directory is always looked in first.
|
20
|
+
"#{Dir.pwd}/#{Repo::REPOSITORY_PATH}:#{ENV['RIPELIB']}".split(/:/)
|
36
21
|
end
|
37
22
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
23
|
+
##
|
24
|
+
# Search throughout the library for a task or workflow component by the
|
25
|
+
# name of +handle+. When there is more than one match, give precendence to
|
26
|
+
# component whose path is declared first.
|
27
|
+
#
|
28
|
+
# @param comp [Symbol] Type of component: either +:workflow+ or
|
29
|
+
# +:task+.
|
30
|
+
# @param handle [String] Name of component
|
31
|
+
# @return [String, nil] Full path of the component if found, and +nil+
|
32
|
+
# otherwise.
|
33
|
+
|
34
|
+
def find(comp, handle)
|
35
|
+
ext = { task: 'sh',
|
36
|
+
workflow: 'rb' }
|
37
|
+
|
38
|
+
search = paths.map do |path|
|
39
|
+
filename = "#{path}/#{comp}s/#{handle}.#{ext[comp]}"
|
40
|
+
(File.exists? filename) ? filename : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
search.compact.first
|
54
44
|
end
|
55
45
|
|
56
|
-
search.compact.first
|
57
46
|
end
|
58
47
|
|
59
48
|
end
|
data/lib/ripe/repo.rb
CHANGED
@@ -1,14 +1,5 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'fileutils'
|
3
|
-
require_relative 'block'
|
4
|
-
require_relative 'worker'
|
5
|
-
require_relative 'worker_controller'
|
6
|
-
require_relative 'worker_migration'
|
7
|
-
require_relative 'working_block'
|
8
|
-
require_relative 'library'
|
9
|
-
require_relative 'liquid_block'
|
10
|
-
require_relative 'task'
|
11
|
-
require_relative 'task_migration'
|
12
3
|
|
13
4
|
module Ripe
|
14
5
|
|
@@ -17,11 +8,12 @@ module Ripe
|
|
17
8
|
# +git+ repository and is the starting point of the package. It
|
18
9
|
# instantiates:
|
19
10
|
#
|
20
|
-
# * a library containing information as to where to retrieve ripe
|
21
|
-
# components such as tasks and workflows;
|
22
11
|
# * a database that contains all worker metadata; and
|
23
|
-
# * a controller that communicates the database
|
24
|
-
# interface.
|
12
|
+
# * a controller that communicates with both the database and the compute
|
13
|
+
# cluster interface.
|
14
|
+
#
|
15
|
+
# @attr_reader controller [WorkerController] a controller that communicates
|
16
|
+
# with both the database and the computer cluster interface.
|
25
17
|
#
|
26
18
|
# @see Ripe::WorkerController
|
27
19
|
# @see Ripe::Library
|
@@ -32,19 +24,27 @@ module Ripe
|
|
32
24
|
DATABASE_PATH = "#{REPOSITORY_PATH}/meta.db"
|
33
25
|
WORKERS_PATH = "#{REPOSITORY_PATH}/workers"
|
34
26
|
|
35
|
-
attr_reader :
|
27
|
+
attr_reader :controller
|
36
28
|
|
37
29
|
##
|
38
|
-
# Initialize a repository
|
30
|
+
# Initialize a repository.
|
39
31
|
|
40
32
|
def initialize
|
41
|
-
@has_repository = File.exists?
|
42
|
-
@
|
43
|
-
|
33
|
+
@has_repository = File.exists? DATABASE_PATH
|
34
|
+
@controller = WorkerController.new
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Return whether the ripe repository exists.
|
39
|
+
#
|
40
|
+
# @return [Boolean] whether the repository exists
|
41
|
+
|
42
|
+
def has_repository?
|
43
|
+
@has_repository
|
44
44
|
end
|
45
45
|
|
46
46
|
##
|
47
|
-
# Attach to an existing database
|
47
|
+
# Attach to an existing database.
|
48
48
|
|
49
49
|
def attach
|
50
50
|
ActiveRecord::Base.establish_connection({
|
@@ -54,14 +54,15 @@ module Ripe
|
|
54
54
|
end
|
55
55
|
|
56
56
|
##
|
57
|
-
# Attach to an existing database, and creates one if
|
57
|
+
# Attach to an existing database, and creates one if a database cannot be
|
58
|
+
# found.
|
58
59
|
|
59
60
|
def attach_or_create
|
60
61
|
@has_repository ? attach : create
|
61
62
|
end
|
62
63
|
|
63
64
|
##
|
64
|
-
# Create a database
|
65
|
+
# Create a database.
|
65
66
|
|
66
67
|
def create
|
67
68
|
FileUtils.mkdir_p(REPOSITORY_PATH)
|
@@ -71,8 +72,8 @@ module Ripe
|
|
71
72
|
attach
|
72
73
|
|
73
74
|
# Create the tables
|
74
|
-
WorkerMigration.up
|
75
|
-
TaskMigration.up
|
75
|
+
DB::WorkerMigration.up
|
76
|
+
DB::TaskMigration.up
|
76
77
|
rescue
|
77
78
|
destroy
|
78
79
|
end
|
data/lib/ripe/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
require_relative '
|
1
|
+
require_relative 'worker_controller/preparer'
|
2
|
+
require_relative 'worker_controller/syncer'
|
3
3
|
|
4
4
|
module Ripe
|
5
5
|
|
@@ -10,177 +10,105 @@ module Ripe
|
|
10
10
|
|
11
11
|
class WorkerController
|
12
12
|
|
13
|
-
include Singleton
|
14
|
-
|
15
13
|
##
|
16
|
-
#
|
14
|
+
# Prepare workers by applying the workflow callback and its parameters to
|
17
15
|
# each sample.
|
18
16
|
#
|
19
17
|
# @see Ripe::DSL::WorkflowDSL#describe
|
18
|
+
# @see Ripe::WorkerController::Preparer
|
20
19
|
#
|
21
|
-
# @param
|
22
|
-
# @
|
23
|
-
# of sample and a hash of parameters provided by the workflow and by the
|
24
|
-
# command line.
|
25
|
-
# @param vars [Hash] a list of worker-wide parameters
|
26
|
-
|
27
|
-
def prepare(samples, callback, vars = {})
|
28
|
-
vars = {
|
29
|
-
wd: Dir.pwd,
|
30
|
-
mode: :patch,
|
31
|
-
group_num: 1,
|
32
|
-
}.merge(vars)
|
33
|
-
|
34
|
-
return if ![:patch, :force, :depend].include? vars[:mode].to_sym
|
35
|
-
|
36
|
-
samples = samples.map do |sample|
|
37
|
-
block = callback.call(sample, vars).prune(vars[:mode].to_sym == :force,
|
38
|
-
vars[:mode].to_sym == :depend)
|
39
|
-
if block != nil
|
40
|
-
puts "Preparing sample #{sample}"
|
41
|
-
[sample, block]
|
42
|
-
else
|
43
|
-
puts "Nothing to do for sample #{sample}"
|
44
|
-
nil
|
45
|
-
end
|
46
|
-
end
|
47
|
-
samples = samples.compact
|
48
|
-
|
49
|
-
samples.each_slice(vars[:group_num].to_i).map do |worker_samples|
|
50
|
-
worker = Worker.create(handle: vars[:handle])
|
51
|
-
|
52
|
-
blocks = worker_samples.map do |sample, block|
|
53
|
-
# Preorder traversal of blocks -- assign incremental numbers starting from
|
54
|
-
# 1 to each node as it is being traversed, as well as producing the job
|
55
|
-
# file for each task.
|
56
|
-
post_var_assign = lambda do |subblock|
|
57
|
-
if subblock.blocks.length == 0
|
58
|
-
# This section is only called when the subblock is actually a working
|
59
|
-
# block (a leaf in the block arborescence).
|
60
|
-
task = worker.tasks.create({
|
61
|
-
sample: sample,
|
62
|
-
block: subblock.id,
|
63
|
-
})
|
64
|
-
|
65
|
-
file = File.new(task.sh, 'w')
|
66
|
-
file.puts subblock.command
|
67
|
-
file.close
|
68
|
-
|
69
|
-
subblock.vars.merge!(log: task.log)
|
70
|
-
else
|
71
|
-
subblock.blocks.each(&post_var_assign)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
post_var_assign.call(block)
|
76
|
-
block
|
77
|
-
end
|
20
|
+
# @param (see Preparer#initialize)
|
21
|
+
# @return [Array<Worker>] workers prepared in current batch
|
78
22
|
|
79
|
-
|
80
|
-
|
81
|
-
stdout: worker.stdout,
|
82
|
-
stderr: worker.stderr,
|
83
|
-
command: SerialBlock.new(*blocks).command,
|
84
|
-
})
|
85
|
-
|
86
|
-
file = File.new(worker.sh, 'w')
|
87
|
-
file.puts LiquidBlock.new("#{PATH}/share/moab.sh", vars).command
|
88
|
-
file.close
|
89
|
-
|
90
|
-
worker.update({
|
91
|
-
status: :prepared,
|
92
|
-
ppn: vars[:ppn],
|
93
|
-
queue: vars[:queue],
|
94
|
-
walltime: vars[:walltime],
|
95
|
-
})
|
96
|
-
worker
|
97
|
-
end
|
23
|
+
def prepare(workflow, samples, params = {})
|
24
|
+
Preparer.new(workflow, samples, params).workers
|
98
25
|
end
|
99
26
|
|
100
27
|
##
|
101
|
-
#
|
28
|
+
# Apply a block to a list of workers.
|
102
29
|
#
|
103
|
-
# @param
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
30
|
+
# @param workers [Array<DB::Worker>, DB::Worker] a list of workers or a
|
31
|
+
# single worker
|
32
|
+
# @return [Array<DB::Worker>] the list of workers given in arguments,
|
33
|
+
# with modified states
|
34
|
+
|
35
|
+
def distribute(workers, &block)
|
36
|
+
workers = [workers] if workers.is_a? DB::Worker
|
37
|
+
workers.map { |w| block.call(w) }
|
108
38
|
end
|
109
39
|
|
110
40
|
##
|
111
|
-
#
|
41
|
+
# Run worker job code into bash locally.
|
112
42
|
#
|
113
|
-
# @param
|
43
|
+
# @param (see #distribute)
|
44
|
+
# @return (see #distribute)
|
114
45
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
46
|
+
def local(workers)
|
47
|
+
distribute workers do |worker|
|
48
|
+
`bash #{worker.sh}`
|
49
|
+
end
|
118
50
|
end
|
119
51
|
|
120
52
|
##
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
}
|
53
|
+
# Submit worker jobs to the compute cluster system.
|
54
|
+
#
|
55
|
+
# @param (see #distribute)
|
56
|
+
# @return (see #distribute)
|
57
|
+
|
58
|
+
def start(workers)
|
59
|
+
distribute workers do |worker|
|
60
|
+
if worker.status == 'prepared'
|
61
|
+
worker.update(status: :queueing,
|
62
|
+
moab_id: `qsub '#{worker.sh}'`.strip.split(/\./).first)
|
63
|
+
else
|
64
|
+
puts "Worker #{worker.id} could not be started: not prepared"
|
133
65
|
end
|
134
66
|
end
|
67
|
+
end
|
135
68
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
status: status, # Queued jobs that appear become either idle, blocked or active
|
150
|
-
})
|
151
|
-
end
|
69
|
+
##
|
70
|
+
# Cancel worker jobs in the compute cluster system.
|
71
|
+
#
|
72
|
+
# @param (see #distribute)
|
73
|
+
# @return (see #distribute)
|
74
|
+
|
75
|
+
def cancel(workers)
|
76
|
+
distribute workers do |worker|
|
77
|
+
if ['queueing', 'idle', 'blocked', 'active'].include? worker.status
|
78
|
+
`canceljob #{worker.moab_id}`
|
79
|
+
worker.update(status: :cancelled)
|
80
|
+
else
|
81
|
+
puts "Worker #{worker.id} could not be cancelled: not started"
|
152
82
|
end
|
153
83
|
end
|
84
|
+
end
|
154
85
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
stdout = File.new(worker.stdout).readlines.join
|
165
|
-
else
|
166
|
-
stdout = ""
|
167
|
-
end
|
168
|
-
worker.update({
|
169
|
-
cpu_used: stdout[/Resources:[ \t]*cput=([0-9]{1,2}(\:[0-9]{2})+),/, 1],
|
170
|
-
exit_code: stdout[/Exit code:[ \t]*(.*)$/, 1],
|
171
|
-
host: stdout[/Nodes:[ \t]*(.*)$/, 1],
|
172
|
-
memory_used: stdout[/Resources:.*,mem=([0-9]*[a-zA-Z]*),/, 1],
|
173
|
-
time: stdout[/Resources:.*,walltime=([0-9]{1,2}(\:[0-9]{2})+)$/, 1],
|
174
|
-
status: :completed,
|
175
|
-
})
|
176
|
-
end
|
177
|
-
end
|
86
|
+
##
|
87
|
+
# Synchronize the status of jobs with the internal list of workers.
|
88
|
+
#
|
89
|
+
# @see Ripe::WorkerController::Syncer
|
90
|
+
#
|
91
|
+
# @return [Array<DB::Worker>] the list of updated workers
|
92
|
+
|
93
|
+
def sync
|
94
|
+
Syncer.new.workers
|
178
95
|
end
|
179
96
|
|
97
|
+
##
|
98
|
+
# List the n most recent workers.
|
99
|
+
#
|
100
|
+
# @param n [Integer] the number of most recent workers
|
101
|
+
# @return [Array<DB::Worker>] the list of +n+ most recent workers
|
102
|
+
|
180
103
|
def list(n = 20)
|
181
|
-
Worker.last(n)
|
104
|
+
DB::Worker.last(n)
|
182
105
|
end
|
183
106
|
|
107
|
+
##
|
108
|
+
# Launch the an interactive text editor from the console.
|
109
|
+
#
|
110
|
+
# @return [void]
|
111
|
+
|
184
112
|
def edit(*args)
|
185
113
|
system("$EDITOR #{args.join(' ')}")
|
186
114
|
end
|