scout-gear 8.0.0 → 9.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.vimproject +48 -9
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/bin/scout +16 -4
- data/doc/lib/scout/path.md +35 -0
- data/doc/lib/scout/workflow/task.md +13 -0
- data/lib/rbbt-scout.rb +2 -1
- data/lib/scout/cmd.rb +32 -29
- data/lib/scout/concurrent_stream.rb +36 -19
- data/lib/scout/exceptions.rb +10 -0
- data/lib/scout/indiferent_hash.rb +17 -0
- data/lib/scout/log/color.rb +11 -11
- data/lib/scout/log/progress/report.rb +8 -5
- data/lib/scout/log/progress/util.rb +3 -0
- data/lib/scout/log/trap.rb +3 -3
- data/lib/scout/log.rb +67 -36
- data/lib/scout/meta_extension.rb +34 -0
- data/lib/scout/misc/digest.rb +11 -2
- data/lib/scout/misc/filesystem.rb +2 -3
- data/lib/scout/misc/format.rb +12 -7
- data/lib/scout/misc/monitor.rb +11 -0
- data/lib/scout/misc/system.rb +48 -0
- data/lib/scout/named_array.rb +8 -0
- data/lib/scout/offsite/ssh.rb +174 -0
- data/lib/scout/offsite/step.rb +100 -0
- data/lib/scout/offsite/sync.rb +55 -0
- data/lib/scout/offsite.rb +3 -0
- data/lib/scout/open/lock.rb +5 -24
- data/lib/scout/open/remote.rb +12 -1
- data/lib/scout/open/stream.rb +109 -122
- data/lib/scout/open/util.rb +9 -0
- data/lib/scout/open.rb +12 -11
- data/lib/scout/path/find.rb +15 -10
- data/lib/scout/path/util.rb +5 -0
- data/lib/scout/path.rb +1 -1
- data/lib/scout/persist/serialize.rb +4 -4
- data/lib/scout/persist.rb +1 -1
- data/lib/scout/resource/open.rb +8 -0
- data/lib/scout/resource/path.rb +16 -9
- data/lib/scout/resource/software.rb +4 -2
- data/lib/scout/resource/util.rb +10 -4
- data/lib/scout/resource.rb +2 -0
- data/lib/scout/tsv/dumper.rb +5 -1
- data/lib/scout/tsv/index.rb +28 -86
- data/lib/scout/tsv/open.rb +35 -14
- data/lib/scout/tsv/parser.rb +22 -5
- data/lib/scout/tsv/persist/tokyocabinet.rb +2 -0
- data/lib/scout/tsv/stream.rb +204 -0
- data/lib/scout/tsv/transformer.rb +11 -0
- data/lib/scout/tsv.rb +9 -2
- data/lib/scout/work_queue/worker.rb +2 -2
- data/lib/scout/work_queue.rb +37 -12
- data/lib/scout/workflow/definition.rb +2 -1
- data/lib/scout/workflow/deployment/orchestrator.rb +254 -0
- data/lib/scout/workflow/deployment.rb +1 -0
- data/lib/scout/workflow/step/dependencies.rb +46 -14
- data/lib/scout/workflow/step/file.rb +5 -0
- data/lib/scout/workflow/step/info.rb +13 -3
- data/lib/scout/workflow/step/inputs.rb +5 -0
- data/lib/scout/workflow/step/load.rb +1 -1
- data/lib/scout/workflow/step/provenance.rb +1 -0
- data/lib/scout/workflow/step/status.rb +27 -9
- data/lib/scout/workflow/step.rb +82 -30
- data/lib/scout/workflow/task/dependencies.rb +116 -0
- data/lib/scout/workflow/task/inputs.rb +36 -17
- data/lib/scout/workflow/task.rb +12 -109
- data/lib/scout/workflow/usage.rb +57 -41
- data/lib/scout/workflow.rb +19 -13
- data/lib/scout-gear.rb +2 -0
- data/lib/scout.rb +6 -0
- data/scout-gear.gemspec +38 -7
- data/scout_commands/doc +37 -0
- data/scout_commands/find +1 -0
- data/scout_commands/offsite +30 -0
- data/scout_commands/resource/produce +66 -0
- data/scout_commands/template +52 -0
- data/scout_commands/update +29 -0
- data/scout_commands/workflow/info +15 -3
- data/scout_commands/workflow/install +105 -0
- data/scout_commands/workflow/task +46 -6
- data/share/software/install_helpers +2 -2
- data/share/templates/command +25 -0
- data/share/templates/workflow.rb +14 -0
- data/test/scout/offsite/test_ssh.rb +15 -0
- data/test/scout/offsite/test_step.rb +32 -0
- data/test/scout/offsite/test_sync.rb +36 -0
- data/test/scout/offsite/test_task.rb +0 -0
- data/test/scout/resource/test_path.rb +6 -0
- data/test/scout/test_named_array.rb +6 -0
- data/test/scout/test_persist.rb +3 -2
- data/test/scout/test_tsv.rb +17 -0
- data/test/scout/test_work_queue.rb +64 -42
- data/test/scout/tsv/persist/test_adapter.rb +1 -1
- data/test/scout/tsv/test_index.rb +14 -0
- data/test/scout/tsv/test_parser.rb +35 -0
- data/test/scout/tsv/test_stream.rb +200 -0
- data/test/scout/tsv/test_transformer.rb +12 -0
- data/test/scout/workflow/deployment/test_orchestrator.rb +272 -0
- data/test/scout/workflow/step/test_dependencies.rb +68 -0
- data/test/scout/workflow/step/test_info.rb +17 -0
- data/test/scout/workflow/step/test_status.rb +0 -1
- data/test/scout/workflow/task/test_dependencies.rb +357 -0
- data/test/scout/workflow/task/test_inputs.rb +52 -0
- data/test/scout/workflow/test_definition.rb +18 -0
- data/test/scout/workflow/test_documentation.rb +24 -0
- data/test/scout/workflow/test_step.rb +109 -0
- data/test/scout/workflow/test_task.rb +0 -287
- data/test/test_scout.rb +9 -0
- metadata +89 -5
- data/scout_commands/workflow/task_old +0 -706
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
class SSHLine
|
4
|
+
class << self
|
5
|
+
attr_accessor :default_server
|
6
|
+
def default_server
|
7
|
+
@@default_server ||= begin
|
8
|
+
ENV["SCOUT_OFFSITE"] || ENV["SCOUT_SERVER"] || 'localhost'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(host = :default, user = nil)
|
14
|
+
host = SSHLine.default_server if host.nil? || host == :default
|
15
|
+
@host = host
|
16
|
+
@user = user
|
17
|
+
|
18
|
+
@ssh = Net::SSH.start(@host, @user)
|
19
|
+
|
20
|
+
@ch = @ssh.open_channel do |ch|
|
21
|
+
ch.exec 'bash -l'
|
22
|
+
end
|
23
|
+
|
24
|
+
@ch.send_data("[[ -f ~/.scout/environment ]] && source ~/.scout/environment\n")
|
25
|
+
@ch.send_data("[[ -f ~/.rbbt/environment ]] && source ~/.rbbt/environment\n")
|
26
|
+
|
27
|
+
@ch.on_data do |_,data|
|
28
|
+
if m = data.match(/DONECMD: (\d+)\n/)
|
29
|
+
@exit_status = m[1].to_i
|
30
|
+
@output << data.sub(m[0],'')
|
31
|
+
serve_output
|
32
|
+
else
|
33
|
+
@output << data
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@ch.on_extended_data do |_,c,err|
|
38
|
+
STDERR.write err
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def self.reach?(server = SSHLine.default_server)
|
44
|
+
Persist.memory(server, :key => "Reach server") do
|
45
|
+
begin
|
46
|
+
CMD.cmd("ssh #{server} bash -l -c \"scout\"")
|
47
|
+
true
|
48
|
+
rescue Exception
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def send_cmd(command)
|
55
|
+
@output = ""
|
56
|
+
@complete_output = false
|
57
|
+
@ch.send_data(command+"\necho DONECMD: $?\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
def serve_output
|
61
|
+
@complete_output = true
|
62
|
+
end
|
63
|
+
|
64
|
+
def run(command)
|
65
|
+
send_cmd(command)
|
66
|
+
@ssh.loop{ ! @complete_output}
|
67
|
+
if @exit_status.to_i == 0
|
68
|
+
return @output
|
69
|
+
else
|
70
|
+
raise SSHProcessFailed.new @host, command
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ruby(script)
|
75
|
+
@output = ""
|
76
|
+
@complete_output = false
|
77
|
+
cmd = "ruby -e \"#{script.gsub('"','\\"')}\"\n"
|
78
|
+
Log.debug "Running ruby on #{@host}:\n#{ script }"
|
79
|
+
@ch.send_data(cmd)
|
80
|
+
@ch.send_data("echo DONECMD: $?\n")
|
81
|
+
@ssh.loop{ !@complete_output }
|
82
|
+
if @exit_status.to_i == 0
|
83
|
+
return @output
|
84
|
+
else
|
85
|
+
raise SSHProcessFailed.new @host, "Ruby script:\n#{script}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def scout(script)
|
90
|
+
scout_script =<<-EOF
|
91
|
+
require 'scout'
|
92
|
+
SSHLine.run_local do
|
93
|
+
#{script.strip}
|
94
|
+
end
|
95
|
+
EOF
|
96
|
+
|
97
|
+
m = ruby(scout_script)
|
98
|
+
Marshal.load m
|
99
|
+
end
|
100
|
+
|
101
|
+
def workflow(workflow, script)
|
102
|
+
preamble =<<-EOF
|
103
|
+
wf = Workflow.require_workflow('#{workflow}')
|
104
|
+
EOF
|
105
|
+
|
106
|
+
scout(preamble + "\n" + script)
|
107
|
+
end
|
108
|
+
|
109
|
+
class Mock < SSHLine
|
110
|
+
def initialize
|
111
|
+
end
|
112
|
+
|
113
|
+
def run(command)
|
114
|
+
CMD.cmd(command)
|
115
|
+
end
|
116
|
+
|
117
|
+
def ruby(script)
|
118
|
+
cmd = "ruby -e \"#{script.gsub('"','\\"')}\"\n"
|
119
|
+
CMD.cmd(cmd)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
@connections = {}
|
124
|
+
def self.open(host, user = nil)
|
125
|
+
@connections[[host, user]] ||=
|
126
|
+
begin
|
127
|
+
if host == 'localhost'
|
128
|
+
SSHLine::Mock.new
|
129
|
+
else
|
130
|
+
SSHLine.new host, user
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.run(server, cmd, options = nil)
|
136
|
+
cmd = cmd * " " if Array === cmd
|
137
|
+
cmd += " " + CMD.process_cmd_options(options) if options
|
138
|
+
open(server).run(cmd)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.ruby(server, script)
|
142
|
+
open(server).ruby(script)
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.scout(server, script)
|
146
|
+
open(server).scout(script)
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.workflow(server, workflow, script)
|
150
|
+
open(server).workflow(workflow, script)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.command(server, command, argv = [], options = nil)
|
154
|
+
command = "scout #{command}" unless command && command.include?('scout')
|
155
|
+
argv_str = (argv - ["--"]).collect{|v| '"' + v.to_s + '"' } * " "
|
156
|
+
command = "#{command} #{argv_str}"
|
157
|
+
Log.debug "Offsite #{server} running: #{command}"
|
158
|
+
run(server, command, options)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.mkdir(server, path)
|
162
|
+
self.run server, "mkdir -p '#{path}'"
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.run_local(&block)
|
166
|
+
res = begin
|
167
|
+
old_stdout = STDOUT.dup; STDOUT.reopen(STDERR)
|
168
|
+
block.call
|
169
|
+
ensure
|
170
|
+
STDOUT.reopen(old_stdout)
|
171
|
+
end
|
172
|
+
puts Marshal.dump(res)
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require_relative '../workflow/step'
|
2
|
+
require_relative 'ssh'
|
3
|
+
require_relative 'sync'
|
4
|
+
|
5
|
+
module OffsiteStep
|
6
|
+
|
7
|
+
extend MetaExtension
|
8
|
+
extension_attr :server, :workflow_name, :clean_id, :slurm
|
9
|
+
|
10
|
+
def inputs_directory
|
11
|
+
@inputs_directory ||= begin
|
12
|
+
if provided_inputs && provided_inputs.any?
|
13
|
+
file = ".scout/tmp/step_inputs/#{workflow}/#{task_name}/#{name}"
|
14
|
+
TmpFile.with_path do |inputs_dir|
|
15
|
+
save_inputs(inputs_dir)
|
16
|
+
SSHLine.rsync(inputs_dir, file, target: server, directory: true)
|
17
|
+
end
|
18
|
+
file
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def workflow_name
|
24
|
+
@workflow_name || workflow.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def offsite_job_ssh(script)
|
28
|
+
parts = []
|
29
|
+
parts << <<~EOF.strip
|
30
|
+
wf = Workflow.require_workflow "#{workflow_name}";
|
31
|
+
EOF
|
32
|
+
|
33
|
+
if inputs_directory
|
34
|
+
parts << <<~EOF.strip
|
35
|
+
job = wf.job(:#{task_name}, "#{clean_name}", :load_inputs => "#{inputs_directory}");
|
36
|
+
EOF
|
37
|
+
else
|
38
|
+
parts << <<~EOF.strip
|
39
|
+
job = wf.job(:#{task_name}, "#{clean_name}");
|
40
|
+
EOF
|
41
|
+
end
|
42
|
+
|
43
|
+
parts << script
|
44
|
+
|
45
|
+
|
46
|
+
SSHLine.scout server, parts * "\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def offsite_path
|
50
|
+
@path = offsite_job_ssh <<~EOF
|
51
|
+
job.path.identify
|
52
|
+
EOF
|
53
|
+
end
|
54
|
+
|
55
|
+
def info
|
56
|
+
info = @info ||= offsite_job_ssh <<~EOF
|
57
|
+
info = Open.exists?(job.info_file) ? job.info : {}
|
58
|
+
info[:running] = true if job.running?
|
59
|
+
info
|
60
|
+
EOF
|
61
|
+
|
62
|
+
@info = nil unless %w(done aborted error).include?(info[:status].to_s)
|
63
|
+
|
64
|
+
info
|
65
|
+
end
|
66
|
+
|
67
|
+
def done?
|
68
|
+
status == :done
|
69
|
+
end
|
70
|
+
|
71
|
+
def orchestrate_slurm
|
72
|
+
bundle_files = offsite_job_ssh <<~EOF
|
73
|
+
require 'rbbt/hpc'
|
74
|
+
HPC::BATCH_MODULE = HPC.batch_system "SLURM"
|
75
|
+
HPC::BATCH_MODULE.orchestrate_job(job, {})
|
76
|
+
job.join
|
77
|
+
job.bundle_files
|
78
|
+
EOF
|
79
|
+
SSHLine.sync(bundle_files, source: server)
|
80
|
+
self.load
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def exec
|
85
|
+
bundle_files = offsite_job_ssh <<~EOF
|
86
|
+
job.run
|
87
|
+
job.bundle_files
|
88
|
+
EOF
|
89
|
+
SSHLine.sync(bundle_files, source: server)
|
90
|
+
self.load
|
91
|
+
end
|
92
|
+
|
93
|
+
def run
|
94
|
+
if slurm
|
95
|
+
orchestrate_slurm
|
96
|
+
else
|
97
|
+
exec
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class SSHLine
|
2
|
+
def self.locate(server, paths, map: :user)
|
3
|
+
SSHLine.scout server, <<-EOF
|
4
|
+
map = :#{map}
|
5
|
+
paths = [#{paths.collect{|p| "'" + p + "'" } * ", " }]
|
6
|
+
located = paths.collect{|p| Path.setup(p).find(map) }
|
7
|
+
identified = paths.collect{|p| Resource.identify(p) }
|
8
|
+
[located, identified]
|
9
|
+
EOF
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.rsync(source_path, target_path, directory: false, source: nil, target: nil, dry_run: false, hard_link: false)
|
13
|
+
rsync_args = "-avztHP --copy-unsafe-links --omit-dir-times "
|
14
|
+
|
15
|
+
rsync_args << "--link-dest '#{source_path}' " if hard_link && ! source
|
16
|
+
|
17
|
+
source_path = source_path + "/" if directory && ! source_path.end_with?("/")
|
18
|
+
target_path = target_path + "/" if directory && ! target_path.end_with?("/")
|
19
|
+
if target
|
20
|
+
SSHLine.mkdir target, File.dirname(target_path)
|
21
|
+
else
|
22
|
+
Open.mkdir(File.dirname(target_path))
|
23
|
+
end
|
24
|
+
|
25
|
+
cmd = 'rsync '
|
26
|
+
cmd << rsync_args
|
27
|
+
cmd << '-nv ' if dry_run
|
28
|
+
cmd << (source ? [source, source_path] * ":" : source_path) << " "
|
29
|
+
cmd << (target ? [target, target_path] * ":" : target_path) << " "
|
30
|
+
|
31
|
+
CMD.cmd_log(cmd, :log => Log::HIGH)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.sync(paths, source: nil, target: nil, map: :user, **kwargs)
|
35
|
+
source = nil if source == 'localhost'
|
36
|
+
target = nil if target == 'localhost'
|
37
|
+
|
38
|
+
if source
|
39
|
+
source_paths, identified_paths = SSHLine.locate(source, paths)
|
40
|
+
else
|
41
|
+
source_paths = paths.collect{|p| Path === p ? p.find : p }
|
42
|
+
identified_paths = paths.collect{|p| Resource.identify(p) }
|
43
|
+
end
|
44
|
+
|
45
|
+
if target
|
46
|
+
target_paths = SSHLine.locate(target, identified_paths, map: map)
|
47
|
+
else
|
48
|
+
target_paths = identified_paths.collect{|p| p.find(map) }
|
49
|
+
end
|
50
|
+
|
51
|
+
source_paths.zip(target_paths).each do |source_path,target_path|
|
52
|
+
rsync(source_path, target_path, source: source, target: target, **kwargs)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/scout/open/lock.rb
CHANGED
@@ -4,27 +4,13 @@ require_relative '../exceptions'
|
|
4
4
|
require_relative 'lock/lockfile'
|
5
5
|
|
6
6
|
module Open
|
7
|
-
def self.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def self.unlock_thread(t, exception = nil)
|
12
|
-
while t.alive? && t.backtrace.select{|l| l.include?("lockfile") }.any?
|
13
|
-
iii :UNLOCK
|
14
|
-
t.raise(exception)
|
15
|
-
Log.stack t.backtrace
|
16
|
-
locks = t["locks"]
|
17
|
-
return if locks.nil? || locks.empty?
|
18
|
-
locks.each do |lock|
|
19
|
-
lock.unlock if lock.locked?
|
20
|
-
Open.rm(lock.path)
|
21
|
-
end
|
22
|
-
iii Thread.current
|
23
|
-
Thread.list.each{|t| iii [t, t["locks"]]; Log.stack t.backtrace }
|
24
|
-
Log.stack t.backtrace
|
25
|
-
end
|
7
|
+
def self.init_lock
|
8
|
+
Lockfile.refresh = 2
|
9
|
+
Lockfile.max_age = 30
|
10
|
+
Lockfile.suspend = 4
|
26
11
|
end
|
27
12
|
|
13
|
+
self.init_lock
|
28
14
|
|
29
15
|
def self.lock(file, unlock = true, options = {})
|
30
16
|
unlock, options = true, unlock if Hash === unlock
|
@@ -57,9 +43,6 @@ module Open
|
|
57
43
|
raise LockInterrupted
|
58
44
|
end
|
59
45
|
|
60
|
-
iii Thread.current["lock_exception"] if Thread.current["lock_exception"]
|
61
|
-
raise Thread.current["lock_exception"] if Thread.current["lock_exception"]
|
62
|
-
|
63
46
|
res = nil
|
64
47
|
|
65
48
|
begin
|
@@ -73,8 +56,6 @@ module Open
|
|
73
56
|
if lockfile.locked?
|
74
57
|
lockfile.unlock
|
75
58
|
end
|
76
|
-
iii Thread.current["lock_exception"] if Thread.current["lock_exception"]
|
77
|
-
raise Thread.current["lock_exception"] if Thread.current["lock_exception"]
|
78
59
|
rescue Exception
|
79
60
|
Log.warn "Exception unlocking: #{lockfile.path}"
|
80
61
|
Log.exception $!
|
data/lib/scout/open/remote.rb
CHANGED
@@ -23,7 +23,11 @@ module Open
|
|
23
23
|
m = file.match(/ssh:\/\/([^:]+):(.*)/)
|
24
24
|
server = m[1]
|
25
25
|
file = m[2]
|
26
|
-
|
26
|
+
if server == 'localhost'
|
27
|
+
Open.open(file)
|
28
|
+
else
|
29
|
+
CMD.cmd("ssh '#{server}' cat '#{file}'", :pipe => true, :autojoin => true)
|
30
|
+
end
|
27
31
|
end
|
28
32
|
|
29
33
|
def self.wget(url, options = {})
|
@@ -121,4 +125,11 @@ module Open
|
|
121
125
|
filename = cache_file(url, options)
|
122
126
|
Open.open(filename)
|
123
127
|
end
|
128
|
+
|
129
|
+
def self.scp(source_file, target_file, target: nil, source: nil)
|
130
|
+
CMD.cmd_log("ssh #{target} mkdir -p #{File.dirname(target_file)}")
|
131
|
+
target_file = [target, target_file] * ":" if target && ! target_file.start_with?(target+":")
|
132
|
+
source_file = [source, source_file] * ":" if source && ! source_file.start_with?(source+":")
|
133
|
+
CMD.cmd_log("scp -r '#{ source_file }' #{target_file}")
|
134
|
+
end
|
124
135
|
end
|