ripe 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -5
  3. data/Guardfile +2 -4
  4. data/README.md +1 -0
  5. data/bin/ripe +1 -59
  6. data/lib/ripe.rb +8 -1
  7. data/lib/ripe/blocks.rb +13 -0
  8. data/lib/ripe/blocks/block.rb +142 -0
  9. data/lib/ripe/blocks/liquid_block.rb +48 -0
  10. data/lib/ripe/blocks/multi_block.rb +71 -0
  11. data/lib/ripe/blocks/parallel_block.rb +29 -0
  12. data/lib/ripe/blocks/serial_block.rb +61 -0
  13. data/lib/ripe/blocks/working_block.rb +101 -0
  14. data/lib/ripe/cli.rb +121 -0
  15. data/lib/ripe/cli/helper.rb +31 -0
  16. data/lib/ripe/db.rb +7 -0
  17. data/lib/ripe/db/task.rb +42 -0
  18. data/lib/ripe/db/task_migration.rb +33 -0
  19. data/lib/ripe/db/worker.rb +64 -0
  20. data/lib/ripe/db/worker_migration.rb +41 -0
  21. data/lib/ripe/dsl.rb +2 -4
  22. data/lib/ripe/dsl/task_dsl.rb +4 -2
  23. data/lib/ripe/dsl/workflow_dsl.rb +5 -0
  24. data/lib/ripe/library.rb +34 -45
  25. data/lib/ripe/repo.rb +24 -23
  26. data/lib/ripe/version.rb +1 -1
  27. data/lib/ripe/worker_controller.rb +72 -144
  28. data/lib/ripe/worker_controller/preparer.rb +172 -0
  29. data/lib/ripe/worker_controller/syncer.rb +118 -0
  30. data/spec/cli_spec.rb +14 -0
  31. data/spec/library_spec.rb +18 -18
  32. data/spec/spec_helper.rb +2 -0
  33. data/spec/testpack.rb +16 -5
  34. data/spec/testpack/.ripe/meta.db +0 -0
  35. data/spec/testpack/.ripe/tasks/bar.sh +3 -0
  36. data/spec/testpack/{ripe → .ripe}/tasks/foo.sh +0 -0
  37. data/spec/testpack/.ripe/workers/1/1.sh +16 -0
  38. data/spec/testpack/.ripe/workers/1/2.sh +16 -0
  39. data/spec/testpack/.ripe/workers/1/job.sh +54 -0
  40. data/spec/testpack/.ripe/workers/2/3.sh +16 -0
  41. data/spec/testpack/.ripe/workers/2/4.sh +16 -0
  42. data/spec/testpack/.ripe/workers/2/job.sh +54 -0
  43. data/spec/testpack/.ripe/workers/3/5.sh +16 -0
  44. data/spec/testpack/.ripe/workers/3/6.sh +16 -0
  45. data/spec/testpack/.ripe/workers/3/job.sh +54 -0
  46. data/spec/testpack/.ripe/workflows/foobar.rb +23 -0
  47. data/spec/testpack/{case/Sample1 → Sample1}/bar_output.txt +0 -0
  48. data/spec/testpack/{case/Sample1 → Sample1}/foo_input.txt +0 -0
  49. data/spec/testpack/{case/Sample1 → Sample1}/foo_output.txt +0 -0
  50. data/spec/testpack/{case/Sample2 → Sample2}/bar_output.txt +0 -0
  51. data/spec/testpack/{case/Sample2 → Sample2}/foo_input.txt +0 -0
  52. data/spec/testpack/{case/Sample2 → Sample2}/foo_output.txt +0 -0
  53. data/spec/testpack/{case/Sample3 → Sample3}/bar_output.txt +0 -0
  54. data/spec/testpack/{case/Sample3 → Sample3}/foo_input.txt +0 -0
  55. data/spec/testpack/{case/Sample3 → Sample3}/foo_output.txt +0 -0
  56. data/spec/worker_controller_spec.rb +143 -0
  57. metadata +66 -40
  58. data/lib/ripe/block.rb +0 -41
  59. data/lib/ripe/liquid_block.rb +0 -17
  60. data/lib/ripe/multi_block.rb +0 -35
  61. data/lib/ripe/parallel_block.rb +0 -13
  62. data/lib/ripe/serial_block.rb +0 -37
  63. data/lib/ripe/task.rb +0 -21
  64. data/lib/ripe/task_migration.rb +0 -18
  65. data/lib/ripe/worker.rb +0 -44
  66. data/lib/ripe/worker_migration.rb +0 -26
  67. data/lib/ripe/working_block.rb +0 -41
  68. data/spec/block_spec.rb +0 -7
  69. data/spec/ripe_spec.rb +0 -7
  70. data/spec/testpack/ripe/tasks/bar.sh +0 -3
  71. data/spec/testpack/ripe/workflows/foobar.rb +0 -23
data/lib/ripe/dsl.rb CHANGED
@@ -1,4 +1,2 @@
1
- require_relative 'repo'
2
- require_relative 'working_block'
3
- require_relative 'dsl/task_dsl.rb'
4
- require_relative 'dsl/workflow_dsl.rb'
1
+ require_relative 'dsl/task_dsl'
2
+ require_relative 'dsl/workflow_dsl'
@@ -33,16 +33,18 @@ module Ripe
33
33
  # @return [WorkingBlock, nil]
34
34
 
35
35
  def task(handle, &block)
36
- filename = Repo.new.library.find_task(handle)
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 accessible
5
- # to ripe (tasks and workflows) based on what is contained in the +RIPELIB+
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
- class Library
8
+ module Library
9
9
 
10
- attr_reader :paths
10
+ class << self
11
11
 
12
- ##
13
- # Create a new library spanning all paths in the +RIPELIB+ environment
14
- # variable.
12
+ ##
13
+ # Provide a list of search paths
14
+ #
15
+ # @return [List] Return the list of search paths
15
16
 
16
- def initialize
17
- # Prepends the working directory to the list of paths so that the
18
- # working directory is always looked in first.
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
- search.compact.first
39
- end
40
-
41
- ##
42
- # Search throughout the library for a workflow component by the name of
43
- # +handle+. When there is more than one match, give precendence to
44
- # component whose path is declared first.
45
- #
46
- # @param handle [String] Workflow to search for
47
- # @return [String, nil] Return the full path of the component if found,
48
- # and +nil+ otherwise.
49
-
50
- def find_workflow(handle)
51
- search = @paths.map do |path|
52
- filename = "#{path}/workflows/#{handle}.rb"
53
- (File.exists? filename) ? filename : nil
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 with the compute cluster
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 :library, :controller
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? REPOSITORY_PATH
42
- @library = Library.new
43
- @controller = WorkerController.instance
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 none is found.
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,3 +1,3 @@
1
1
  module Ripe
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -1,5 +1,5 @@
1
- require 'singleton'
2
- require_relative 'worker'
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
- # Prepares workers by applying the workflow callback and its parameters to
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 samples [List] list of samples to apply the callback to
22
- # @param callback [Proc] a callback function that takes as arguments the name
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
- vars = vars.merge({
80
- name: worker.id,
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
- # Submit a job to the compute cluster system
28
+ # Apply a block to a list of workers.
102
29
  #
103
- # @param worker [Worker] the worker to submit
104
-
105
- def start(worker)
106
- worker.update(status: :queueing,
107
- moab_id: `qsub '#{worker.sh}'`.strip.split(/\./).first)
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
- # Cancel a job in the compute cluster system
41
+ # Run worker job code into bash locally.
112
42
  #
113
- # @param worker [Worker] the worker to cancel
43
+ # @param (see #distribute)
44
+ # @return (see #distribute)
114
45
 
115
- def cancel(worker)
116
- `canceljob #{worker.moab_id}`
117
- worker.update(status: :cancelled)
46
+ def local(workers)
47
+ distribute workers do |worker|
48
+ `bash #{worker.sh}`
49
+ end
118
50
  end
119
51
 
120
52
  ##
121
- # Synchronize the status of jobs with the internal list of workers.
122
-
123
- def sync
124
- lists = {idle: '-i', blocked: '-b', active: '-r'}
125
- lists = lists.map do |status, op|
126
- showq = `showq -u $(whoami) #{op} | grep $(whoami)`.split("\n")
127
- showq.map do |job|
128
- {
129
- moab_id: job[/^([0-9]+) /, 1],
130
- time: job[/ ([0-9]{1,2}(\:[0-9]{2})+) /, 1],
131
- status: status,
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
- # Update status
137
- lists = lists.inject(&:+).each do |job|
138
- moab_id = job[:moab_id]
139
- time = job[:time]
140
- status = job[:status]
141
- worker = Worker.find_by(moab_id: moab_id)
142
-
143
- if worker
144
- worker.update(time: time)
145
- unless ['cancelled', status].include? worker.status
146
- checkjob = `checkjob #{moab_id}`
147
- worker.update({
148
- host: checkjob[/Allocated Nodes:\n\[(.*):[0-9]+\]\n/, 1],
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
- # Mark workers that were previously in active, blocked or idle as completed
156
- # if they cannot be found anymore.
157
- jobs = lists.map { |job| job[:moab_id] }
158
- Worker.where('status in (:statuses)',
159
- :statuses => ['active', 'idle', 'blocked']).each do |worker|
160
- if jobs.include? worker.moab_id
161
- jobs.delete(worker.moab_id) # Remove from list
162
- elsif (worker.status != 'cancelled')
163
- if File.exists? worker.stdout
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