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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +48 -9
  3. data/Rakefile +6 -1
  4. data/VERSION +1 -1
  5. data/bin/scout +16 -4
  6. data/doc/lib/scout/path.md +35 -0
  7. data/doc/lib/scout/workflow/task.md +13 -0
  8. data/lib/rbbt-scout.rb +2 -1
  9. data/lib/scout/cmd.rb +32 -29
  10. data/lib/scout/concurrent_stream.rb +36 -19
  11. data/lib/scout/exceptions.rb +10 -0
  12. data/lib/scout/indiferent_hash.rb +17 -0
  13. data/lib/scout/log/color.rb +11 -11
  14. data/lib/scout/log/progress/report.rb +8 -5
  15. data/lib/scout/log/progress/util.rb +3 -0
  16. data/lib/scout/log/trap.rb +3 -3
  17. data/lib/scout/log.rb +67 -36
  18. data/lib/scout/meta_extension.rb +34 -0
  19. data/lib/scout/misc/digest.rb +11 -2
  20. data/lib/scout/misc/filesystem.rb +2 -3
  21. data/lib/scout/misc/format.rb +12 -7
  22. data/lib/scout/misc/monitor.rb +11 -0
  23. data/lib/scout/misc/system.rb +48 -0
  24. data/lib/scout/named_array.rb +8 -0
  25. data/lib/scout/offsite/ssh.rb +174 -0
  26. data/lib/scout/offsite/step.rb +100 -0
  27. data/lib/scout/offsite/sync.rb +55 -0
  28. data/lib/scout/offsite.rb +3 -0
  29. data/lib/scout/open/lock.rb +5 -24
  30. data/lib/scout/open/remote.rb +12 -1
  31. data/lib/scout/open/stream.rb +109 -122
  32. data/lib/scout/open/util.rb +9 -0
  33. data/lib/scout/open.rb +12 -11
  34. data/lib/scout/path/find.rb +15 -10
  35. data/lib/scout/path/util.rb +5 -0
  36. data/lib/scout/path.rb +1 -1
  37. data/lib/scout/persist/serialize.rb +4 -4
  38. data/lib/scout/persist.rb +1 -1
  39. data/lib/scout/resource/open.rb +8 -0
  40. data/lib/scout/resource/path.rb +16 -9
  41. data/lib/scout/resource/software.rb +4 -2
  42. data/lib/scout/resource/util.rb +10 -4
  43. data/lib/scout/resource.rb +2 -0
  44. data/lib/scout/tsv/dumper.rb +5 -1
  45. data/lib/scout/tsv/index.rb +28 -86
  46. data/lib/scout/tsv/open.rb +35 -14
  47. data/lib/scout/tsv/parser.rb +22 -5
  48. data/lib/scout/tsv/persist/tokyocabinet.rb +2 -0
  49. data/lib/scout/tsv/stream.rb +204 -0
  50. data/lib/scout/tsv/transformer.rb +11 -0
  51. data/lib/scout/tsv.rb +9 -2
  52. data/lib/scout/work_queue/worker.rb +2 -2
  53. data/lib/scout/work_queue.rb +37 -12
  54. data/lib/scout/workflow/definition.rb +2 -1
  55. data/lib/scout/workflow/deployment/orchestrator.rb +254 -0
  56. data/lib/scout/workflow/deployment.rb +1 -0
  57. data/lib/scout/workflow/step/dependencies.rb +46 -14
  58. data/lib/scout/workflow/step/file.rb +5 -0
  59. data/lib/scout/workflow/step/info.rb +13 -3
  60. data/lib/scout/workflow/step/inputs.rb +5 -0
  61. data/lib/scout/workflow/step/load.rb +1 -1
  62. data/lib/scout/workflow/step/provenance.rb +1 -0
  63. data/lib/scout/workflow/step/status.rb +27 -9
  64. data/lib/scout/workflow/step.rb +82 -30
  65. data/lib/scout/workflow/task/dependencies.rb +116 -0
  66. data/lib/scout/workflow/task/inputs.rb +36 -17
  67. data/lib/scout/workflow/task.rb +12 -109
  68. data/lib/scout/workflow/usage.rb +57 -41
  69. data/lib/scout/workflow.rb +19 -13
  70. data/lib/scout-gear.rb +2 -0
  71. data/lib/scout.rb +6 -0
  72. data/scout-gear.gemspec +38 -7
  73. data/scout_commands/doc +37 -0
  74. data/scout_commands/find +1 -0
  75. data/scout_commands/offsite +30 -0
  76. data/scout_commands/resource/produce +66 -0
  77. data/scout_commands/template +52 -0
  78. data/scout_commands/update +29 -0
  79. data/scout_commands/workflow/info +15 -3
  80. data/scout_commands/workflow/install +105 -0
  81. data/scout_commands/workflow/task +46 -6
  82. data/share/software/install_helpers +2 -2
  83. data/share/templates/command +25 -0
  84. data/share/templates/workflow.rb +14 -0
  85. data/test/scout/offsite/test_ssh.rb +15 -0
  86. data/test/scout/offsite/test_step.rb +32 -0
  87. data/test/scout/offsite/test_sync.rb +36 -0
  88. data/test/scout/offsite/test_task.rb +0 -0
  89. data/test/scout/resource/test_path.rb +6 -0
  90. data/test/scout/test_named_array.rb +6 -0
  91. data/test/scout/test_persist.rb +3 -2
  92. data/test/scout/test_tsv.rb +17 -0
  93. data/test/scout/test_work_queue.rb +64 -42
  94. data/test/scout/tsv/persist/test_adapter.rb +1 -1
  95. data/test/scout/tsv/test_index.rb +14 -0
  96. data/test/scout/tsv/test_parser.rb +35 -0
  97. data/test/scout/tsv/test_stream.rb +200 -0
  98. data/test/scout/tsv/test_transformer.rb +12 -0
  99. data/test/scout/workflow/deployment/test_orchestrator.rb +272 -0
  100. data/test/scout/workflow/step/test_dependencies.rb +68 -0
  101. data/test/scout/workflow/step/test_info.rb +17 -0
  102. data/test/scout/workflow/step/test_status.rb +0 -1
  103. data/test/scout/workflow/task/test_dependencies.rb +357 -0
  104. data/test/scout/workflow/task/test_inputs.rb +52 -0
  105. data/test/scout/workflow/test_definition.rb +18 -0
  106. data/test/scout/workflow/test_documentation.rb +24 -0
  107. data/test/scout/workflow/test_step.rb +109 -0
  108. data/test/scout/workflow/test_task.rb +0 -287
  109. data/test/test_scout.rb +9 -0
  110. metadata +89 -5
  111. 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
@@ -0,0 +1,3 @@
1
+ require_relative 'offsite/ssh'
2
+ require_relative 'offsite/step'
3
+ require_relative 'offsite/sync'
@@ -4,27 +4,13 @@ require_relative '../exceptions'
4
4
  require_relative 'lock/lockfile'
5
5
 
6
6
  module Open
7
- def self.thread_in_lock(t)
8
- t.backtrace.select{|l| l.include?("lockfile") }.any?
9
- end
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 $!
@@ -23,7 +23,11 @@ module Open
23
23
  m = file.match(/ssh:\/\/([^:]+):(.*)/)
24
24
  server = m[1]
25
25
  file = m[2]
26
- CMD.cmd("ssh '#{server}' cat '#{file}'", :pipe => true, :autojoin => true)
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