autoflow 0.6.4 → 0.8.0
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.
- data/autoflow.gemspec +2 -0
- data/bin/AutoFlow +35 -4
- data/bin/flow_logger +315 -0
- data/lib/autoflow/logging.rb +58 -0
- data/lib/autoflow/queue_manager.rb +77 -23
- data/lib/autoflow/queue_managers/bash_manager.rb +10 -9
- data/lib/autoflow/stack.rb +11 -9
- data/lib/autoflow/version.rb +1 -1
- metadata +37 -2
data/autoflow.gemspec
CHANGED
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'net-ssh', '>= 2.8.0'
|
22
|
+
spec.add_runtime_dependency 'git', '>= 1.3.0'
|
22
23
|
spec.add_runtime_dependency 'win32console', '>= 1.3.2' if !ENV['OS'].nil? && ENV['OS'].downcase.include?('windows')
|
23
24
|
spec.add_runtime_dependency 'colorize', '~> 0.7.3'
|
25
|
+
spec.add_runtime_dependency 'terminal-table', '~> 1.6.0'
|
24
26
|
spec.add_development_dependency "bundler", "~> 1.3"
|
25
27
|
spec.add_development_dependency "rake"
|
26
28
|
end
|
data/bin/AutoFlow
CHANGED
@@ -11,6 +11,7 @@ require 'io/console'
|
|
11
11
|
require 'net/ssh'
|
12
12
|
require 'queue_manager'
|
13
13
|
require 'fileutils'
|
14
|
+
require 'git'
|
14
15
|
|
15
16
|
#################################################################################################
|
16
17
|
# METHODS
|
@@ -21,8 +22,13 @@ def get_templates(string_template)
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def get_repositories
|
24
|
-
|
25
|
+
main_repository = File.join(ENV['HOME'], 'autoflow_templates')
|
26
|
+
local_rep = File.join(main_repository, 'local')
|
27
|
+
remote_rep = File.join(main_repository, 'remote')
|
28
|
+
all_remote = Dir.glob(File.join(remote_rep, '*'))
|
29
|
+
directories = [local_rep]
|
25
30
|
directories.concat(ENV['WORKFLOW_REPOSITORY'].split(':')) if !ENV['WORKFLOW_REPOSITORY'].nil?
|
31
|
+
directories.concat(all_remote)
|
26
32
|
return directories
|
27
33
|
end
|
28
34
|
|
@@ -39,6 +45,8 @@ def list_repository_templates
|
|
39
45
|
end
|
40
46
|
templates.delete('.')
|
41
47
|
templates.delete('..')
|
48
|
+
templates.delete('README.md') # From git remote repos
|
49
|
+
templates.delete('.git') # From git remote repos
|
42
50
|
return templates
|
43
51
|
end
|
44
52
|
|
@@ -105,6 +113,11 @@ optparse = OptionParser.new do |opts|
|
|
105
113
|
options[:graph] = graph
|
106
114
|
end
|
107
115
|
|
116
|
+
options[:get_template_repository] = nil
|
117
|
+
opts.on( '-G', '--get_template_repository GIT_URL', 'Git url to get template from a remote repository' ) do |url|
|
118
|
+
options[:get_template_repository] = url
|
119
|
+
end
|
120
|
+
|
108
121
|
options[:identifier] = FALSE
|
109
122
|
opts.on( '-i', '--job_identifier STRING', 'Identifier tag for each launching script' ) do |identifier|
|
110
123
|
options[:identifier] = identifier
|
@@ -204,8 +217,24 @@ optparse.parse!
|
|
204
217
|
# MAIN
|
205
218
|
#################################################################################################
|
206
219
|
#Create repository
|
207
|
-
|
208
|
-
|
220
|
+
main_repository = File.join(ENV['HOME'], 'autoflow_templates')
|
221
|
+
local_rep = File.join(main_repository, 'local')
|
222
|
+
remote_rep = File.join(main_repository, 'remote')
|
223
|
+
Dir.mkdir(main_repository) if !File.exists?(main_repository)
|
224
|
+
Dir.mkdir(local_rep) if !File.exists?(local_rep)
|
225
|
+
Dir.mkdir(remote_rep) if !File.exists?(remote_rep)
|
226
|
+
|
227
|
+
#Move templates from legacy tree folder to current tree folder
|
228
|
+
Dir.glob(File.join(main_repository, '*')) { |file| FileUtils.mv(file, local_rep) if file != local_rep && file != remote_rep }
|
229
|
+
|
230
|
+
# Get git remote repos and include it in user templates
|
231
|
+
if !options[:get_template_repository].nil?
|
232
|
+
fields = options[:get_template_repository].split(/\/+/)
|
233
|
+
repo_name = fields.last.gsub('.git','')
|
234
|
+
repo_author = fields[-2]
|
235
|
+
local_name = "REM_#{repo_author}_#{repo_name}"
|
236
|
+
Git.clone(options[:get_template_repository], local_name, :path => remote_rep)
|
237
|
+
exit_exec('Remote template repository added as ' + local_name)
|
209
238
|
end
|
210
239
|
|
211
240
|
# List templates
|
@@ -294,7 +323,7 @@ end
|
|
294
323
|
# Flow parse
|
295
324
|
#--------------------------------------------------------------------------------
|
296
325
|
stack = Stack.new(exec_folder, options)
|
297
|
-
|
326
|
+
stack.parse!
|
298
327
|
|
299
328
|
#--------------------------------------------------------------------------------
|
300
329
|
# Flow exec
|
@@ -304,6 +333,8 @@ if !options[:graph].nil?
|
|
304
333
|
else
|
305
334
|
stack.inspect if options[:verbose]
|
306
335
|
stack.comment_main_command if options[:comment]
|
336
|
+
options[:write_sh] = TRUE # Set in flow logger to FALSE, it is used for relaunch failed jobs
|
337
|
+
manager = QueueManager.select_queue_manager(stack.exec_folder, options, stack.jobs, stack.persist_variables)
|
307
338
|
manager.exec
|
308
339
|
end
|
309
340
|
options[:ssh].close if options[:remote]
|
data/bin/flow_logger
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
ROOT_PATH=File.dirname(__FILE__)
|
4
|
+
$: << File.expand_path(File.join(ROOT_PATH, "..", "lib", "autoflow"))
|
5
|
+
$: << File.expand_path(File.join(ROOT_PATH, "..", "lib", "autoflow", "queue_managers"))
|
6
|
+
|
7
|
+
require 'autoflow'
|
8
|
+
require 'optparse'
|
9
|
+
require 'colorize'
|
10
|
+
require 'logging'
|
11
|
+
require 'json'
|
12
|
+
require 'terminal-table'
|
13
|
+
require 'queue_manager'
|
14
|
+
require 'program'
|
15
|
+
require 'erb'
|
16
|
+
|
17
|
+
#################################################################################################
|
18
|
+
### METHODS
|
19
|
+
#################################################################################################
|
20
|
+
|
21
|
+
def report_log(log, initial_flow_attribs, mode, workflow_status, no_size)
|
22
|
+
set_task_state(log, workflow_status)
|
23
|
+
set_time(log)
|
24
|
+
if mode.nil? || mode.upcase == 'ALL'
|
25
|
+
tasks = log
|
26
|
+
else
|
27
|
+
tasks = log.select{|name, attribs| attribs['state'] == mode.upcase}
|
28
|
+
end
|
29
|
+
rows = []
|
30
|
+
tasks.each do |task_name, attribs|
|
31
|
+
job_path = initial_flow_attribs[task_name].first
|
32
|
+
size = nil
|
33
|
+
size = `du -sh #{job_path}`.split.first if !no_size
|
34
|
+
rows << [attribs['state_msg'], File.basename(job_path), attribs['time'], size, task_name]
|
35
|
+
end
|
36
|
+
puts Terminal::Table.new :headings => ['Status', 'Folder', 'Time', 'Size', 'Job Name'], :rows => rows
|
37
|
+
end
|
38
|
+
|
39
|
+
def launch_failed_jobs(log, initial_flow_attribs, exec_folder, batch)
|
40
|
+
options = {
|
41
|
+
:verbose => FALSE,
|
42
|
+
:identifier => nil,
|
43
|
+
:remote => FALSE,
|
44
|
+
:ssh => nil,
|
45
|
+
:external_dependencies => [],
|
46
|
+
:batch => batch,
|
47
|
+
:write_sh => FALSE
|
48
|
+
}
|
49
|
+
failed_jobs = get_failed_jobs(log)
|
50
|
+
jobs = {}
|
51
|
+
create_jobs(jobs, failed_jobs, initial_flow_attribs)
|
52
|
+
get_all_dependencies(jobs, failed_jobs, initial_flow_attribs)
|
53
|
+
manager = QueueManager.select_queue_manager(exec_folder, options, jobs, {})
|
54
|
+
manager.exec
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_jobs(jobs, failed_jobs, initial_flow_attribs)
|
58
|
+
failed_jobs.each do |job|
|
59
|
+
folder, dependencies = initial_flow_attribs[job]
|
60
|
+
job_attrib = {
|
61
|
+
:done => FALSE,
|
62
|
+
:folder => TRUE,
|
63
|
+
:buffer => FALSE,
|
64
|
+
:exec_folder => folder,
|
65
|
+
:cpu_asign => nil
|
66
|
+
}
|
67
|
+
verified_dependencies = []
|
68
|
+
dependencies.each do |dep|
|
69
|
+
verified_dependencies << dep if !jobs[dep].nil?
|
70
|
+
end
|
71
|
+
jobs[job] = Program.new(job, '', '', verified_dependencies, job_attrib)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_all_dependencies(jobs, failed_jobs, initial_flow_attribs)
|
76
|
+
failed_dependecies = []
|
77
|
+
failed_jobs.each do |fj|
|
78
|
+
initial_flow_attribs.each do |job, attribs|
|
79
|
+
folder, dependencies = attribs
|
80
|
+
failed_dependecies << job if dependencies.include?(fj) && !failed_dependecies.include?(job)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
if !failed_dependecies.empty?
|
84
|
+
create_jobs(jobs, failed_dependecies, initial_flow_attribs)
|
85
|
+
get_all_dependencies(jobs, failed_dependecies, initial_flow_attribs)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_failed_jobs(log)
|
90
|
+
failed_jobs = []
|
91
|
+
position = 0
|
92
|
+
fails = []
|
93
|
+
log.each do |task, attribs|
|
94
|
+
abort = find_failed(attribs['start'], attribs['end'])
|
95
|
+
if !abort.nil?
|
96
|
+
position = abort if abort > position
|
97
|
+
fails << [task, abort]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
if !fails.empty?
|
101
|
+
fails.each do |task, index|
|
102
|
+
if position == index
|
103
|
+
failed_jobs << task
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return failed_jobs
|
108
|
+
end
|
109
|
+
|
110
|
+
def find_failed(ar_start, ar_end)
|
111
|
+
position = nil
|
112
|
+
ar_start.reverse.each_with_index do |st, i|
|
113
|
+
reverse_pos = ar_start.length - i - 1
|
114
|
+
stop = ar_end[reverse_pos]
|
115
|
+
if st > 0 && stop == 0
|
116
|
+
next_executions = ar_end[reverse_pos..ar_end.length - 1]
|
117
|
+
if next_executions.nil? || next_executions.count(0) == next_executions.length
|
118
|
+
position = reverse_pos
|
119
|
+
break
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
return position
|
124
|
+
end
|
125
|
+
|
126
|
+
def set_time(log)
|
127
|
+
log.each do |task, attribs|
|
128
|
+
start = attribs['start'].last
|
129
|
+
stop = attribs['end'].last
|
130
|
+
status = attribs['state']
|
131
|
+
time = 0
|
132
|
+
if status == 'SUCC'
|
133
|
+
time = stop - start
|
134
|
+
elsif status == 'RUN'
|
135
|
+
time = Time.now.to_i - start
|
136
|
+
end
|
137
|
+
attribs['seconds'] = time
|
138
|
+
magnitude = 's'
|
139
|
+
if time >= 60
|
140
|
+
magnitude = 'm'
|
141
|
+
time = time /60.0 # To minutes
|
142
|
+
end
|
143
|
+
if time >= 60 && magnitude == 'm'
|
144
|
+
magnitude = 'h'
|
145
|
+
time = time /60.0 # To hours
|
146
|
+
end
|
147
|
+
if time >= 24 && magnitude == 'h'
|
148
|
+
magnitude = 'd'
|
149
|
+
time = time /24.0 # To days
|
150
|
+
end
|
151
|
+
if time == 0
|
152
|
+
time_string = '-'
|
153
|
+
else
|
154
|
+
time_string = "#{time} #{magnitude}"
|
155
|
+
end
|
156
|
+
attribs['time'] = time_string
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def set_task_state(log, workflow_status, position = -1)
|
161
|
+
log.each do | task, attribs|
|
162
|
+
start_position = attribs['start'].length - position
|
163
|
+
start = attribs['start'][position]
|
164
|
+
stop_position = attribs['end'].length - position
|
165
|
+
stop = attribs['end'][position]
|
166
|
+
if workflow_status # Workflow has finished
|
167
|
+
if start == 0 && stop == 0
|
168
|
+
status = 'NOT'
|
169
|
+
status_msg = 'NOT'.colorize(:blue)
|
170
|
+
elsif start > 0 && stop > 0
|
171
|
+
status = 'SUCC'
|
172
|
+
status_msg = 'SUCC'.colorize(:green)
|
173
|
+
elsif start > 0 && stop ==0
|
174
|
+
status = 'ABORT'
|
175
|
+
status_msg = 'ABORT'.colorize(:red)
|
176
|
+
end
|
177
|
+
else # Workflow is still running
|
178
|
+
if start == 0 && stop == 0
|
179
|
+
status = 'PEND'
|
180
|
+
status_msg = 'PEND'.colorize(:blue)
|
181
|
+
elsif start > 0 && stop > 0
|
182
|
+
status = 'SUCC'
|
183
|
+
status_msg = 'SUCC'.colorize(:green)
|
184
|
+
elsif start > 0 && stop ==0
|
185
|
+
status = 'RUN'
|
186
|
+
status_msg = 'RUN'.colorize(:magenta)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
attribs['state'] = status
|
190
|
+
attribs['state_msg'] = status_msg
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def add_timestamp(log_file, attrib, task_name)
|
195
|
+
File.open(log_file, 'a'){|f| f.puts "#{task_name}\t#{attrib}\t#{Time.now.to_i}"}
|
196
|
+
end
|
197
|
+
|
198
|
+
def report_html(log, initial_flow_attribs)
|
199
|
+
set_task_state(log, TRUE)
|
200
|
+
set_time(log)
|
201
|
+
report ="
|
202
|
+
<table>
|
203
|
+
<% log.each do |task, attribs| %>
|
204
|
+
<tr>
|
205
|
+
<td><%= task %></td>
|
206
|
+
<td><%= attribs['seconds'] %></td>
|
207
|
+
<tr>
|
208
|
+
<% end %>
|
209
|
+
</table>
|
210
|
+
"
|
211
|
+
data_structure = {
|
212
|
+
'y' =>{
|
213
|
+
'vars' => ['Time'],
|
214
|
+
'smps' => log.keys,
|
215
|
+
'data' => [log.values.map{|attribs| attribs['seconds']}],
|
216
|
+
'desc' => ['seconds']
|
217
|
+
},
|
218
|
+
'a' => {
|
219
|
+
"xAxis" => ["Time"]
|
220
|
+
}
|
221
|
+
}
|
222
|
+
#puts log.inspect
|
223
|
+
|
224
|
+
puts data_structure.inspect
|
225
|
+
#renderer = ERB.new(report)
|
226
|
+
demo = File.open('lines.html').read
|
227
|
+
demo.gsub!('data_structure', data_structure.to_json)
|
228
|
+
renderer = ERB.new(report + "\n" + demo)
|
229
|
+
File.open('report.html', 'w'){|f| f.puts renderer.result()}
|
230
|
+
end
|
231
|
+
#################################################################################################
|
232
|
+
### PARSE OPTIONS
|
233
|
+
#################################################################################################
|
234
|
+
|
235
|
+
options = {}
|
236
|
+
OptionParser.new do |opts|
|
237
|
+
opts.banner = "Usage: __FILE__ [options]"
|
238
|
+
|
239
|
+
options[:workflow_execution] = Dir.pwd
|
240
|
+
opts.on("-e", "--workflow_execution PATH", "Path to workflow directory") do |opt|
|
241
|
+
options[:workflow_execution] = File.expand_path(opt)
|
242
|
+
end
|
243
|
+
|
244
|
+
options[:start] = nil
|
245
|
+
opts.on("-s", "--start TASK_NAME", "Write start timestamp of TASK_NAME to log") do |opt|
|
246
|
+
options[:start] = opt
|
247
|
+
end
|
248
|
+
|
249
|
+
options[:finish] = nil
|
250
|
+
opts.on("-f", "--finish TASK_NAME", "Write finish timestamp of TASK_NAME to log") do |opt|
|
251
|
+
options[:finish] = opt
|
252
|
+
end
|
253
|
+
|
254
|
+
options[:report] = nil
|
255
|
+
opts.on("-r", "--report STATUS", "List the status of launched tasks.") do |opt|
|
256
|
+
options[:report] = opt
|
257
|
+
end
|
258
|
+
|
259
|
+
options[:workflow_status] = FALSE
|
260
|
+
opts.on("-w", "--workflow_finished", "When set, logger assumes that the workflow has ended") do |opt|
|
261
|
+
options[:workflow_status] = TRUE
|
262
|
+
end
|
263
|
+
|
264
|
+
options[:no_size] = FALSE
|
265
|
+
opts.on("-n", "--no_size", "When set, logger don't compute the workflow folder sizes") do |opt|
|
266
|
+
options[:no_size] = TRUE
|
267
|
+
end
|
268
|
+
|
269
|
+
options[:batch] = FALSE
|
270
|
+
opts.on( '-b', '--batch', 'Workflow execution using batch' ) do |opt|
|
271
|
+
options[:batch] = TRUE
|
272
|
+
end
|
273
|
+
|
274
|
+
options[:launch_failed_jobs] = FALSE
|
275
|
+
opts.on("-l", "--launch_failed_jobs", "Launch jobs tagges as ABORT and NOT. This option only works when the -w flag is enabled") do |opt|
|
276
|
+
options[:launch_failed_jobs] = TRUE
|
277
|
+
end
|
278
|
+
|
279
|
+
options[:html] = FALSE
|
280
|
+
opts.on("-H", "--html", "Make a workflow execution full report in html format") do |opt|
|
281
|
+
options[:html] = TRUE
|
282
|
+
end
|
283
|
+
end.parse!
|
284
|
+
|
285
|
+
#################################################################################################
|
286
|
+
### MAIN
|
287
|
+
#################################################################################################
|
288
|
+
|
289
|
+
if !options[:start].nil?
|
290
|
+
add_timestamp(options[:workflow_execution],'start', options[:start])
|
291
|
+
elsif !options[:finish].nil?
|
292
|
+
add_timestamp(options[:workflow_execution],'end', options[:finish])
|
293
|
+
else
|
294
|
+
log_folder = File.join(options[:workflow_execution], '.wf_log')
|
295
|
+
job_attribs_file = File.join(options[:workflow_execution], 'wf.json')
|
296
|
+
|
297
|
+
if !Dir.exists?(log_folder)
|
298
|
+
puts "Log folder not exists"
|
299
|
+
Process.exit
|
300
|
+
end
|
301
|
+
if !File.exists?(job_attribs_file)
|
302
|
+
puts "wf.json file not exists"
|
303
|
+
Process.exit
|
304
|
+
end
|
305
|
+
|
306
|
+
attribs = JSON.parse(File.open(job_attribs_file).read)
|
307
|
+
log = parse_log(log_folder)
|
308
|
+
if !options[:report].nil?
|
309
|
+
report_log(log, attribs, options[:report], options[:workflow_status], options[:no_size])
|
310
|
+
elsif options[:html]
|
311
|
+
report_html(log, attribs)
|
312
|
+
elsif options[:workflow_status] && options[:launch_failed_jobs]
|
313
|
+
launch_failed_jobs(log, attribs, options[:workflow_execution], options[:batch])
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
def parse_log(log_path)
|
2
|
+
log = {}
|
3
|
+
if Dir.exists?(log_path)
|
4
|
+
Dir.entries(log_path).each do |entry|
|
5
|
+
next if entry == '.' || entry == '..'
|
6
|
+
File.open(File.join(log_path, entry)).each do |line|
|
7
|
+
line.chomp!
|
8
|
+
name, status, time_int = line.split("\t")
|
9
|
+
time = time_int.to_i
|
10
|
+
query = log[name]
|
11
|
+
if query.nil?
|
12
|
+
log[name] = {status => [time]}
|
13
|
+
else
|
14
|
+
query_status = query[status]
|
15
|
+
if query_status.nil?
|
16
|
+
query[status] = [time]
|
17
|
+
else
|
18
|
+
query[status] << time
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
log.each do |task, attribs|
|
25
|
+
#puts "#{attribs.inspect}"
|
26
|
+
set_length = attribs['set'].length
|
27
|
+
fill_attrib(attribs, 'start', set_length)
|
28
|
+
fill_attrib(attribs, 'end', set_length)
|
29
|
+
end
|
30
|
+
return log
|
31
|
+
end
|
32
|
+
|
33
|
+
def fill_attrib(attribs, mode, set_length)
|
34
|
+
query = attribs[mode]
|
35
|
+
if query.nil?
|
36
|
+
attribs[mode] = Array.new(set_length, 0)
|
37
|
+
elsif query.length < set_length
|
38
|
+
(set_length - query.length).times do
|
39
|
+
query << 0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def write_log(log, log_path, job_relations_with_folders)
|
45
|
+
Dir.mkdir(log_path) if !Dir.exists?(log_path)
|
46
|
+
job_relations_with_folders.each do |name, folder_deps|
|
47
|
+
if !log[name].nil? #Control check when the wk_log folder has been deleted
|
48
|
+
folder, deps = folder_deps
|
49
|
+
f = File.open([log_path, File.basename(folder)].join('/'), 'w')
|
50
|
+
log[name].each do |mode, times|
|
51
|
+
times.each do |time|
|
52
|
+
f.puts "#{name}\t#{mode}\t#{time}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
f.close
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require 'json'
|
1
3
|
class QueueManager
|
2
4
|
|
3
5
|
def initialize(exec_folder, options, commands, persist_variables)
|
@@ -9,7 +11,9 @@ class QueueManager
|
|
9
11
|
@files = {}
|
10
12
|
@remote = options[:remote]
|
11
13
|
@ssh = options[:ssh]
|
14
|
+
@write_sh = options[:write_sh]
|
12
15
|
@external_dependencies = options[:external_dependencies]
|
16
|
+
@active_jobs = []
|
13
17
|
end
|
14
18
|
|
15
19
|
########################################################################################
|
@@ -20,7 +24,7 @@ class QueueManager
|
|
20
24
|
ObjectSpace.each_object(Class).select { |klass| klass < self }
|
21
25
|
end
|
22
26
|
|
23
|
-
def self.select_queue_manager(
|
27
|
+
def self.select_queue_manager(exec_folder, options, jobs, persist_variables)
|
24
28
|
path_managers = File.join(File.dirname(__FILE__),'queue_managers')
|
25
29
|
Dir.glob(path_managers+'/*').each do |manager|
|
26
30
|
require manager
|
@@ -30,7 +34,7 @@ class QueueManager
|
|
30
34
|
else
|
31
35
|
queue_manager = select_manager(options)
|
32
36
|
end
|
33
|
-
return queue_manager.new(
|
37
|
+
return queue_manager.new(exec_folder, options, jobs, persist_variables)
|
34
38
|
end
|
35
39
|
|
36
40
|
def self.select_manager(options)
|
@@ -60,9 +64,42 @@ class QueueManager
|
|
60
64
|
close_file('index_execution')
|
61
65
|
end
|
62
66
|
|
67
|
+
def init_log #TODO adapt to remote execution
|
68
|
+
log_path = [@exec_folder, '.wf_log'].join('/') #Join must assume linux systems so File.join canot be used for windows hosts
|
69
|
+
log = parse_log(log_path) #TODO modify to folder
|
70
|
+
job_relations_with_folders = get_relations_and_folders
|
71
|
+
if @write_sh
|
72
|
+
create_file('wf.json', @exec_folder)
|
73
|
+
write_file('wf.json', job_relations_with_folders.to_json)
|
74
|
+
close_file('wf.json')
|
75
|
+
end
|
76
|
+
@active_jobs.each do |task|
|
77
|
+
query = log[task]
|
78
|
+
if query.nil?
|
79
|
+
log[task] = {'set' => [Time.now.to_i]}
|
80
|
+
else
|
81
|
+
log[task]['set'] << Time.now.to_i
|
82
|
+
end
|
83
|
+
end
|
84
|
+
write_log(log, log_path, job_relations_with_folders)
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_relations_and_folders
|
88
|
+
relations = {}
|
89
|
+
@commands.each do |name, job|
|
90
|
+
relations[name] = [job.attrib[:exec_folder], job.dependencies]
|
91
|
+
end
|
92
|
+
return relations
|
93
|
+
end
|
94
|
+
|
63
95
|
def launch_all_jobs
|
64
96
|
buffered_jobs = []
|
65
|
-
|
97
|
+
sorted_jobs = sort_jobs_by_dependencies
|
98
|
+
sorted_jobs.each do |name, job|
|
99
|
+
@active_jobs << job.name if !job.attrib[:done]
|
100
|
+
end
|
101
|
+
init_log
|
102
|
+
sorted_jobs.each do |name, job|
|
66
103
|
write_file('index_execution', "#{name}\t#{job.attrib[:exec_folder]}")
|
67
104
|
if job.attrib[:done]
|
68
105
|
next
|
@@ -70,7 +107,7 @@ class QueueManager
|
|
70
107
|
rm_done_dependencies(job)
|
71
108
|
end
|
72
109
|
buffered_jobs = launch_job_in_folder(job, name, buffered_jobs)
|
73
|
-
end
|
110
|
+
end
|
74
111
|
end
|
75
112
|
|
76
113
|
def sort_jobs_by_dependencies # We need job ids from queue system so we ask for each job and we give the previous queue system ids as dependencies if necessary
|
@@ -113,35 +150,41 @@ class QueueManager
|
|
113
150
|
|
114
151
|
|
115
152
|
def launch2queue_system(job, id, buffered_jobs)
|
116
|
-
# Write sh file
|
117
|
-
#--------------------------------
|
118
|
-
log_folder = File.join(@exec_folder, 'log')
|
119
153
|
sh_name = job.name+'.sh'
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
154
|
+
if @write_sh
|
155
|
+
# Write sh file
|
156
|
+
#--------------------------------
|
157
|
+
create_file(sh_name, job.attrib[:exec_folder])
|
158
|
+
write_file(sh_name, '#!/usr/bin/env bash')
|
159
|
+
write_file(sh_name, '##JOB_GROUP_ID='+@job_identifier)
|
160
|
+
write_header(id, job, sh_name)
|
161
|
+
end
|
124
162
|
|
125
163
|
#Get dependencies
|
126
164
|
#------------------------------------
|
127
165
|
ar_dependencies = get_dependencies(job, id)
|
128
166
|
buffered_jobs.each do |id_buff_job, buff_job|
|
129
|
-
write_job(buff_job, sh_name)
|
130
167
|
ar_dependencies += get_dependencies(buff_job, id_buff_job)
|
131
|
-
|
168
|
+
if @write_sh
|
169
|
+
write_job(buff_job, sh_name)
|
170
|
+
buff_job.attrib[:exec_folder] = job.attrib[:exec_folder]
|
171
|
+
end
|
132
172
|
end
|
133
173
|
ar_dependencies.uniq!
|
134
174
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
175
|
+
if @write_sh
|
176
|
+
#Write sh body
|
177
|
+
#--------------------------------
|
178
|
+
write_file(sh_name, 'hostname')
|
179
|
+
log_file_path = [@exec_folder, '.wf_log', File.basename(job.attrib[:exec_folder])].join('/')
|
180
|
+
write_file(sh_name, "flow_logger -e #{log_file_path} -s #{job.name}")
|
181
|
+
write_file(sh_name, "source #{File.join(@exec_folder, 'env_file')}") if !@persist_variables.empty?
|
182
|
+
write_job(job, sh_name)
|
183
|
+
write_file(sh_name, "flow_logger -e #{log_file_path} -f #{job.name}")
|
184
|
+
write_file(sh_name, "echo 'General time'")
|
185
|
+
write_file(sh_name, "times")
|
186
|
+
close_file(sh_name, 0755)
|
187
|
+
end
|
145
188
|
|
146
189
|
#Submit node
|
147
190
|
#-----------------------------------
|
@@ -190,6 +233,17 @@ class QueueManager
|
|
190
233
|
end
|
191
234
|
end
|
192
235
|
|
236
|
+
def read_file(file_path)
|
237
|
+
content = nil
|
238
|
+
if @remote
|
239
|
+
res = @ssh.exec!("[ ! -f #{file_path} ] && echo 'Autoflow:File Not Found' || cat #{file_path}")
|
240
|
+
content = res if !content.include?('Autoflow:File Not Found')
|
241
|
+
else
|
242
|
+
content = File.open(file_path).read if File.exists?(file_path)
|
243
|
+
end
|
244
|
+
return content
|
245
|
+
end
|
246
|
+
|
193
247
|
def system_call(cmd, path = nil)
|
194
248
|
cmd = "cd #{path}; " + cmd if !path.nil?
|
195
249
|
if @remote
|
@@ -4,7 +4,8 @@ class BashManager < QueueManager
|
|
4
4
|
def initialize(exec_folder, options, commands, persist_variables)
|
5
5
|
super
|
6
6
|
@queued = []
|
7
|
-
@
|
7
|
+
@count = 0
|
8
|
+
@pids = {}
|
8
9
|
@path2execution_script = File.join(@exec_folder, 'execution.sh')
|
9
10
|
create_file('execution.sh', @exec_folder)
|
10
11
|
write_file('execution.sh', '#! /usr/bin/env bash')
|
@@ -17,22 +18,22 @@ class BashManager < QueueManager
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def write_header(id, node, sh)
|
20
|
-
|
21
|
+
#@queued << id # For dependencies purposes
|
21
22
|
end
|
22
23
|
|
23
24
|
def submit_job(job, ar_dependencies)
|
24
25
|
write_file('execution.sh','')
|
25
26
|
if !ar_dependencies.empty?
|
26
|
-
|
27
|
-
|
28
|
-
write_file('execution.sh',
|
29
|
-
@last_deps.concat(@queued)
|
27
|
+
ar_dependencies.each do |dep|
|
28
|
+
cmd = "wait \"$pid#{@pids[dep]}\"\nif [ $? -ne 0 ]\nthen \n\techo \"#{job.name} failed\"\n\texit\nfi"
|
29
|
+
write_file('execution.sh', cmd)
|
30
30
|
end
|
31
31
|
end
|
32
|
-
@last_deps.concat(ar_dependencies)
|
33
|
-
@last_deps.uniq!
|
34
32
|
write_file('execution.sh', "cd #{job.attrib[:exec_folder]}")
|
35
|
-
write_file('execution.sh', "./#{job.name}.sh &")
|
33
|
+
write_file('execution.sh', "./#{job.name}.sh &> task_log & pid#{@count}=$!")
|
34
|
+
@pids[job.name] = @count
|
35
|
+
@count += 1
|
36
|
+
@queued << job.name # For dependencies purposes
|
36
37
|
return nil
|
37
38
|
end
|
38
39
|
|
data/lib/autoflow/stack.rb
CHANGED
@@ -27,16 +27,17 @@ class Stack
|
|
27
27
|
@exec_folder = exec_folder #TODO move this to queue_manager
|
28
28
|
@do_retry = options[:retry]
|
29
29
|
@options = options
|
30
|
-
|
31
|
-
@
|
30
|
+
@workflow = options[:workflow]
|
31
|
+
@external_variables= options[:Variables]
|
32
|
+
@jobs = {}
|
32
33
|
end
|
33
34
|
|
34
|
-
def parse
|
35
|
+
def parse!
|
35
36
|
#Clean template
|
36
|
-
workflow.gsub!(/\#.+$/,'') #Delete comments
|
37
|
-
workflow.gsub!("\t",'') #Drop tabs
|
38
|
-
workflow.gsub!(/\n+/,"\n") #Drop empty lines
|
39
|
-
workflow.gsub!(/^\s*/,'')
|
37
|
+
@workflow.gsub!(/\#.+$/,'') #Delete comments
|
38
|
+
@workflow.gsub!("\t",'') #Drop tabs
|
39
|
+
@workflow.gsub!(/\n+/,"\n") #Drop empty lines
|
40
|
+
@workflow.gsub!(/^\s*/,'')
|
40
41
|
|
41
42
|
#Parse template
|
42
43
|
variables_lines = []
|
@@ -44,7 +45,7 @@ class Stack
|
|
44
45
|
node_lines = []
|
45
46
|
|
46
47
|
node_beg = FALSE
|
47
|
-
workflow.each_line do |line|
|
48
|
+
@workflow.each_line do |line|
|
48
49
|
node_beg = TRUE if line.include?('{') # This check the context of a variable
|
49
50
|
if line.include?('}') # if a variable is within a node,
|
50
51
|
if node_beg # we consider tha is a bash variable not a static autoflow variable
|
@@ -62,9 +63,10 @@ class Stack
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
load_variables(variables_lines, @variables)
|
65
|
-
load_variables(external_variables, @variables)
|
66
|
+
load_variables(@external_variables, @variables)
|
66
67
|
load_variables(persist_variables_lines, @persist_variables)
|
67
68
|
parse_nodes(node_lines)
|
69
|
+
@jobs = get_jobs_relations
|
68
70
|
end
|
69
71
|
|
70
72
|
def load_variables(variables_lines, variable_type)
|
data/lib/autoflow/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: autoflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2017-01-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-ssh
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 2.8.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: git
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.3.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.3.0
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
47
|
name: colorize
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -43,6 +59,22 @@ dependencies:
|
|
43
59
|
- - ~>
|
44
60
|
- !ruby/object:Gem::Version
|
45
61
|
version: 0.7.3
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: terminal-table
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.6.0
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.6.0
|
46
78
|
- !ruby/object:Gem::Dependency
|
47
79
|
name: bundler
|
48
80
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,6 +114,7 @@ email:
|
|
82
114
|
executables:
|
83
115
|
- AutoFlow
|
84
116
|
- env_manager
|
117
|
+
- flow_logger
|
85
118
|
- flow_time
|
86
119
|
extensions: []
|
87
120
|
extra_rdoc_files: []
|
@@ -94,9 +127,11 @@ files:
|
|
94
127
|
- autoflow.gemspec
|
95
128
|
- bin/AutoFlow
|
96
129
|
- bin/env_manager
|
130
|
+
- bin/flow_logger
|
97
131
|
- bin/flow_time
|
98
132
|
- lib/autoflow.rb
|
99
133
|
- lib/autoflow/batch.rb
|
134
|
+
- lib/autoflow/logging.rb
|
100
135
|
- lib/autoflow/program.rb
|
101
136
|
- lib/autoflow/queue_manager.rb
|
102
137
|
- lib/autoflow/queue_managers/bash_manager.rb
|