miga-base 0.5.4.0 → 0.5.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7b5a581810fbc79f9808fea7353882960f26f005ff9cc37ccad7483db96f1a4
4
- data.tar.gz: dc4d7163c3b6a396e0ca71325d2cb995001b65a467305c918bfc810e161ddae7
3
+ metadata.gz: 59649a6c1b7aaa4e3c0db090a828469e9595eba42d311eff114417741a2816bd
4
+ data.tar.gz: a765a214e5b64d3f1758ae50942dc7f0649f438e8c2b12312648012d9cceeacc
5
5
  SHA512:
6
- metadata.gz: 71e0e8fa7a6e10ac0396a7e19580e66cd8561979234ffb565c0727e02e9050fd04b94c416d56ce35c8b4c618eeb69fb781d52914df26182c01a295b8481cfad9
7
- data.tar.gz: 359ad12300c7a1aa14b3f66a2bef2d875c24ea689ab66f1f16990f35618ec03b3126f849039ffcb5ef0a0f52c328eaa5e81edf0bc862aa78104652a9cf73216a
6
+ metadata.gz: f76b1691a30d7ce94314a4bd8d22601d86d3101c2db33f262b53548bba817d17bc783f2c93ec0502844adda1f5a17bd0c530e82c82c7fb590f07abf8ffc6788c
7
+ data.tar.gz: c27cd47089f65633cefaca74cd217967b583df43d60682dce9f755a6610fdc6e728638d8d7b884f4de627de79b48080c8325f7b4d60794253f69511c15f997a4
@@ -36,6 +36,10 @@ class MiGA::Cli::Action::Daemon < MiGA::Cli::Action
36
36
  '--max-jobs INT',
37
37
  'Maximum number of jobs to use simultaneously'
38
38
  ) { |v| cli[:maxjobs] = v.to_i }
39
+ opt.on(
40
+ '--node-list PATH',
41
+ 'Path to the list of execution hostnames'
42
+ ) { |v| cli[:nodelist] = v }
39
43
  opt.on(
40
44
  '--ppn INT',
41
45
  'Maximum number of cores to use in a single job'
@@ -70,7 +74,7 @@ class MiGA::Cli::Action::Daemon < MiGA::Cli::Action
70
74
  def perform
71
75
  p = cli.load_project
72
76
  d = MiGA::Daemon.new(p, cli[:json])
73
- [:latency, :maxjobs, :ppn, :shutdown_when_done].each do |k|
77
+ [:latency, :maxjobs, :nodelist, :ppn, :shutdown_when_done].each do |k|
74
78
  d.runopts(k, cli[k]) unless cli[k].nil?
75
79
  end
76
80
  d.daemon(cli.operation, cli[:daemon_opts])
@@ -26,7 +26,7 @@ class MiGA::Cli::Action::Init < MiGA::Cli::Action
26
26
  ){ |v| cli[:mytaxa] = v }
27
27
  opt.on(
28
28
  '--daemon-type STRING',
29
- 'Type of daemon launcher, one of: bash, qsub, msub, slurm',
29
+ 'Type of daemon launcher, one of: bash, ssh, qsub, msub, slurm',
30
30
  "By default: interactive (#{cli[:dtype]} if --auto)"
31
31
  ){ |v| cli[:dtype] = v.to_sym }
32
32
  opt.on(
@@ -13,15 +13,18 @@ module MiGA::Cli::Action::Init::DaemonHelper
13
13
  v = {created: Time.now.to_s, updated: Time.now.to_s}
14
14
  v[:type] = cli.ask_user(
15
15
  'Please select the type of daemon you want to setup',
16
- cli[:dtype], %w(bash qsub msub slurm))
16
+ cli[:dtype], %w(bash ssh qsub msub slurm))
17
17
  case v[:type]
18
18
  when 'bash'
19
19
  v = configure_bash_daemon(v)
20
+ when 'ssh'
21
+ v = configure_ssh_daemon(v)
20
22
  when 'slurm'
21
23
  v = configure_slurm_daemon(v)
22
24
  else # [qm]sub
23
25
  v = configure_qsub_msub_daemon(v)
24
26
  end
27
+ v[:format_version] = 1
25
28
  File.open(daemon_f, 'w') { |fh| fh.puts JSON.pretty_generate(v) }
26
29
  end
27
30
  cli.puts ''
@@ -34,19 +37,51 @@ module MiGA::Cli::Action::Init::DaemonHelper
34
37
  cli.puts 'Setting up internal daemon defaults.'
35
38
  cli.puts 'If you don\'t understand this just leave default values:'
36
39
  v[:cmd] = cli.ask_user(
37
- "How should I launch tasks?\n %1$s: script path, " \
38
- "%2$s: variables, %3$d: CPUs, %4$s: log file, %5$s: task name.\n",
39
- "%2$s '%1$s' > '%4$s' 2>&1")
40
+ "How should I launch tasks?\n" \
41
+ " {{variables}}: script, vars, cpus, log, task_name, miga\n ",
42
+ "{{vars}} {{miga}} run -r '{{script}}' -l '{{log}}' -e")
40
43
  v[:var] = cli.ask_user(
41
- "How should I pass variables?\n %1$s: keys, %2$s: values.\n",
42
- "%1$s=%2$s")
44
+ "How should I pass variables?\n" \
45
+ " {{variables}}: key, value\n ",
46
+ "{{key}}={{value}}")
43
47
  v[:varsep] = cli.ask_user('What should I use to separate variables?', ' ')
44
48
  v[:alive] = cli.ask_user(
45
- "How can I know that a process is still alive?\n %1$s: PID, " \
46
- "output should be 1 for running and 0 for non-running.\n",
47
- "ps -p '%1$s'|tail -n+2|wc -l")
49
+ "How can I know that a process is still alive?\n" \
50
+ " Output should be 1 for running and 0 for non-running\n" \
51
+ " {{variables}}: pid\n ",
52
+ "ps -p '{{pid}}' | tail -n +2 | wc -l")
48
53
  v[:kill] = cli.ask_user(
49
- "How should I terminate tasks?\n %s: process ID.", "kill -9 '%s'")
54
+ "How should I terminate tasks?\n" \
55
+ " {{variables}}: pid\n ",
56
+ "kill -9 '{{pid}}'")
57
+ v
58
+ end
59
+
60
+ def configure_ssh_daemon(v)
61
+ v[:latency] = cli.ask_user('How long should I sleep? (in secs)', '3').to_i
62
+ v[:nodelist] = cli.ask_user(
63
+ 'What environmental variable points to node list?', '$MIGA_NODELIST')
64
+ v[:ppn] = cli.ask_user('How many CPUs can I use per job?', '2').to_i
65
+ cli.puts 'Setting up internal daemon defaults.'
66
+ cli.puts 'If you don\'t understand this just leave default values:'
67
+ v[:cmd] = cli.ask_user(
68
+ "How should I launch tasks?\n" \
69
+ " {{variables}}: script, vars, cpus, log, task_name, miga, host\n ",
70
+ "{{vars}} {{miga}} run -r '{{script}}' -l '{{log}}' -R {{host}} -e")
71
+ v[:var] = cli.ask_user(
72
+ "How should I pass variables?\n" \
73
+ " {{variables}}: key, value\n ",
74
+ "{{key}}={{value}}")
75
+ v[:varsep] = cli.ask_user('What should I use to separate variables?', ' ')
76
+ v[:alive] = cli.ask_user(
77
+ "How can I know that a process is still alive?\n" \
78
+ " Output should be 1 for running and 0 for non-running\n" \
79
+ " {{variables}}: pid\n ",
80
+ "ps -p '{{pid}}' | tail -n +2 | wc -l")
81
+ v[:kill] = cli.ask_user(
82
+ "How should I terminate tasks?\n" \
83
+ " {{variables}}: pid\n ",
84
+ "kill -9 '{{pid}}'")
50
85
  v
51
86
  end
52
87
 
@@ -58,24 +93,28 @@ module MiGA::Cli::Action::Init::DaemonHelper
58
93
  cli.puts 'Setting up internal daemon defaults'
59
94
  cli.puts 'If you don\'t understand this just leave default values:'
60
95
  v[:cmd] = cli.ask_user(
61
- "How should I launch tasks?\n %1$s: script path, " \
62
- "%2$s: variables, %3$d: CPUs, %4$d: log file, %5$s: task name.\n",
63
- "%2$s sbatch --partition='#{queue}' --export=ALL " \
64
- "--nodes=1 --ntasks-per-node=%3$d --output='%4$s' " \
65
- "--job-name='%5$s' --mem=9G --time=12:00:00 %1$s " \
96
+ "How should I launch tasks?\n" \
97
+ " {{variables}}: script, vars, cpus, log, task_name, miga\n ",
98
+ "{{vars}} sbatch --partition='#{queue}' --export=ALL " \
99
+ "--nodes=1 --ntasks-per-node={{cpus}} --output='{{log}}' " \
100
+ "--job-name='{{task_name}}' --mem=9G --time=12:00:00 {{script}} " \
66
101
  "| perl -pe 's/.* //'")
67
102
  v[:var] = cli.ask_user(
68
- "How should I pass variables?\n %1$s: keys, %2$s: values.\n",
69
- "%1$s=%2$s")
103
+ "How should I pass variables?\n" \
104
+ " {{variables}}: key, value\n ",
105
+ "{{key}}={{value}}")
70
106
  v[:varsep] = cli.ask_user(
71
107
  'What should I use to separate variables?', ' ')
72
108
  v[:alive] = cli.ask_user(
73
- "How can I know that a process is still alive?\n %1$s: job id, " \
74
- "output should be 1 for running and 0 for non-running.\n",
75
- "squeue -h -o %%t -j '%1$s' | grep '^PD\\|R\\|CF\\|CG$' " \
109
+ "How can I know that a process is still alive?\n" \
110
+ " Output should be 1 for running and 0 for non-running\n" \
111
+ " {{variables}}: pid\n ",
112
+ "squeue -h -o %t -j '{{pid}}' | grep '^PD\\|R\\|CF\\|CG$' " \
76
113
  "| tail -n 1 | wc -l")
77
114
  v[:kill] = cli.ask_user(
78
- "How should I terminate tasks?\n %s: process ID.", "scancel '%s'")
115
+ "How should I terminate tasks?\n" \
116
+ " {{variables}}: pid\n ",
117
+ "scancel '{{pid}}'")
79
118
  v
80
119
  end
81
120
 
@@ -87,36 +126,40 @@ module MiGA::Cli::Action::Init::DaemonHelper
87
126
  cli.puts 'Setting up internal daemon defaults.'
88
127
  cli.puts 'If you don\'t understand this just leave default values:'
89
128
  v[:cmd] = cli.ask_user(
90
- "How should I launch tasks?\n %1$s: script path, " \
91
- "%2$s: variables, %3$d: CPUs, %4$d: log file, %5$s: task name.\n",
92
- "#{v[:type]} -q '#{queue}' -v '%2$s' -l nodes=1:ppn=%3$d %1$s " \
93
- "-j oe -o '%4$s' -N '%5$s' -l mem=9g -l walltime=12:00:00 " \
94
- "| grep .")
129
+ "How should I launch tasks?\n" \
130
+ " {{variables}}: script, vars, cpus, log, task_name\n ",
131
+ "#{v[:type]} -q '#{queue}' -v '{{vars}}' -l nodes=1:ppn={{cpus}} " \
132
+ "{{script}} -j oe -o '{{log}}' -N '{{task_name}}' -l mem=9g " \
133
+ "-l walltime=12:00:00 | grep .")
95
134
  v[:var] = cli.ask_user(
96
- "How should I pass variables?\n %1$s: keys, %2$s: values.\n",
97
- "%1$s=%2$s")
135
+ "How should I pass variables?\n" \
136
+ " {{variables}}: key, value\n ",
137
+ "{{key}}={{value}}")
98
138
  v[:varsep] = cli.ask_user(
99
139
  'What should I use to separate variables?', ',')
100
140
  if v[:type] == 'qsub'
101
141
  v[:alive] = cli.ask_user(
102
- "How can I know that a process is still alive?\n " \
103
- "%1$s: job id, output should be 1 for running and " \
104
- "0 for non-running.\n",
105
- "qstat -f '%1$s'|grep ' job_state ='|perl -pe 's/.*= //'" \
106
- "|grep '[^C]'|tail -n1|wc -l|awk '{print $1}'")
142
+ "How can I know that a process is still alive?\n" \
143
+ " Output should be 1 for running and 0 for non-running\n" \
144
+ " {{variables}}: pid\n ",
145
+ "qstat -f '{{pid}}' | grep ' job_state =' | perl -pe 's/.*= //' " \
146
+ "| grep '[^C]' | tail -n 1 | wc -l | awk '{print $1}'")
107
147
  v[:kill] = cli.ask_user(
108
- "How should I terminate tasks?\n %s: process ID.", "qdel '%s'")
148
+ "How should I terminate tasks?\n" \
149
+ " {{variables}}: pid\n ",
150
+ "qdel '{{pid}}'")
109
151
  else # msub
110
152
  v[:alive] = cli.ask_user(
111
- "How can I know that a process is still alive?\n " \
112
- "%1$s: job id, output should be 1 for running and " \
113
- "0 for non-running.\n",
114
- "checkjob '%1$s'|grep '^State:'|perl -pe 's/.*: //'" \
115
- "|grep 'Deferred\\|Hold\\|Idle\\|Starting\\|Running\\|Blocked'" \
116
- "|tail -n1|wc -l|awk '{print $1}'")
153
+ "How can I know that a process is still alive?\n" \
154
+ " Output should be 1 for running and 0 for non-running\n" \
155
+ " {{variables}}: pid\n ",
156
+ "checkjob '{{pid}}'|grep '^State:' | perl -pe 's/.*: //' " \
157
+ "| grep 'Deferred\\|Hold\\|Idle\\|Starting\\|Running\\|Blocked'" \
158
+ "| tail -n 1 | wc -l | awk '{print $1}'")
117
159
  v[:kill] = cli.ask_user(
118
- "How should I terminate tasks?\n %s: process ID.",
119
- "canceljob '%s'")
160
+ "How should I terminate tasks?\n" \
161
+ " {{variables}}: pid\n ",
162
+ "canceljob '{{pid}}'")
120
163
  end
121
164
  v
122
165
  end
@@ -7,7 +7,7 @@ require 'shellwords'
7
7
  class MiGA::Cli::Action::Run < MiGA::Cli::Action
8
8
 
9
9
  def parse_cli
10
- cli.defaults = {try_load: false, thr: 1}
10
+ cli.defaults = { try_load: false, thr: 1, env: false }
11
11
  cli.parse do |opt|
12
12
  cli.opt_object(opt, [:project, :dataset_opt, :result])
13
13
  opt.on(
@@ -23,10 +23,20 @@ class MiGA::Cli::Action::Run < MiGA::Cli::Action
23
23
  '-l', '--log PATH',
24
24
  'Path to the output log file to be created. If not set, STDOUT'
25
25
  ) { |v| cli[:log] = v }
26
+ opt.on(
27
+ '-e', '--environment',
28
+ 'Load PROJECT, DATASET, and CORES from the environment'
29
+ ) { |v| cli[:env] = v }
26
30
  end
27
31
  end
28
32
 
29
33
  def perform
34
+ if cli[:env]
35
+ cli[:project] ||= ENV['PROJECT']
36
+ cli[:dataset] ||= ENV['DATASET']
37
+ cli[:thr] ||= ENV['CORES'].to_i unless ENV['CORES'].nil?
38
+ cli[:result] = File.basename(cli[:result].to_s, '.bash').to_sym
39
+ end
30
40
  virtual_task = false
31
41
  miga = MiGA.root_path
32
42
  p = cli.load_project
@@ -43,10 +53,11 @@ class MiGA::Cli::Action::Run < MiGA::Cli::Action
43
53
  end
44
54
  cmd << MiGA.script_path(cli[:result], miga: miga, project: p).shellescape
45
55
  if cli[:remote]
46
- cmd.unshift '.', '/etc/profile', ';'
47
- cmd = ['ssh', cli[:remote].shellescape, cmd.join(' ').shellescape]
56
+ #cmd.unshift '.', '/etc/profile', ';'
57
+ cmd = ['ssh', '-t', '-t', cli[:remote].shellescape,
58
+ cmd.join(' ').shellescape]
48
59
  end
49
- cmd << ['&>', cli[:log].shellescape] if cli[:log]
60
+ cmd << ['>', cli[:log].shellescape, '2>&1'] if cli[:log]
50
61
  pid = spawn cmd.join(' ')
51
62
  Process.wait pid
52
63
  end
@@ -136,4 +136,12 @@ class String
136
136
  def wrap_width(width)
137
137
  gsub(/([^\n\r]{1,#{width}})/, "\\1\n")
138
138
  end
139
+
140
+ ##
141
+ # Replace {{variables}} using the +vars+ hash
142
+ def miga_variables(vars)
143
+ o = "#{self}"
144
+ vars.each { |k, v| o.gsub!("{{#{k}}}", v.to_s) }
145
+ o
146
+ end
139
147
  end
data/lib/miga/daemon.rb CHANGED
@@ -42,9 +42,12 @@ class MiGA::Daemon < MiGA::MiGA
42
42
  def initialize(project, json = nil)
43
43
  $_MIGA_DAEMON_LAIR << self
44
44
  @project = project
45
+ @runopts = {}
45
46
  json ||= File.expand_path('daemon/daemon.json', project.path)
46
- @runopts = MiGA::Json.parse(
47
- json, default: File.expand_path('.miga_daemon.json', ENV['MIGA_HOME']))
47
+ MiGA::Json.parse(
48
+ json, default: File.expand_path('.miga_daemon.json', ENV['MIGA_HOME'])
49
+ ).each { |k,v| runopts(k, v) }
50
+ update_format_0 unless runopts(:format_version)
48
51
  @jobs_to_run = []
49
52
  @jobs_running = []
50
53
  @loop_i = -1
@@ -145,8 +148,8 @@ class MiGA::Daemon < MiGA::MiGA
145
148
  ##
146
149
  # Add the task to the internal queue with symbol key +job+. If the task is
147
150
  # dataset-specific, +ds+ specifies the dataset. To submit jobs to the
148
- # scheduler (or to bash) see #flush!.
149
- def queue_job(job, ds=nil)
151
+ # scheduler (or to bash or ssh) see #flush!
152
+ def queue_job(job, ds = nil)
150
153
  return nil unless get_job(job, ds).nil?
151
154
  ds_name = (ds.nil? ? 'miga-project' : ds.name)
152
155
  say 'Queueing %s:%s' % [ds_name, job]
@@ -160,19 +163,16 @@ class MiGA::Daemon < MiGA::MiGA
160
163
  log_dir = File.expand_path("daemon/#{job}", project.path)
161
164
  Dir.mkdir(log_dir) unless Dir.exist? log_dir
162
165
  task_name = "#{project.metadata[:name][0..9]}:#{job}:#{ds_name}"
163
- to_run = {ds: ds, ds_name: ds_name, job: job, task_name: task_name,
164
- cmd: sprintf(runopts(:cmd),
165
- # 1: script
166
- MiGA::MiGA.script_path(job, miga:vars['MIGA'], project:project),
167
- # 2: vars
168
- vars.keys.map { |k| sprintf(runopts(:var), k, vars[k]) }.
169
- join(runopts(:varsep)),
170
- # 3: CPUs
171
- ppn,
172
- # 4: log file
173
- File.expand_path("#{ds_name}.log", log_dir),
174
- # 5: task name
175
- task_name)}
166
+ to_run = { ds: ds, ds_name: ds_name, job: job, task_name: task_name }
167
+ to_run[:cmd] = runopts(:cmd).miga_variables(
168
+ script: MiGA::MiGA.script_path(job, miga:vars['MIGA'], project: project),
169
+ vars: vars.map { |k, v|
170
+ runopts(:var).miga_variables(key: k, value: v) }.join(runopts(:varsep)),
171
+ cpus: ppn,
172
+ log: File.expand_path("#{ds_name}.log", log_dir),
173
+ task_name: task_name,
174
+ miga: File.expand_path('bin/miga', MiGA::MiGA.root_path).shellescape
175
+ )
176
176
  @jobs_to_run << to_run
177
177
  end
178
178
 
@@ -191,7 +191,7 @@ class MiGA::Daemon < MiGA::MiGA
191
191
 
192
192
  ##
193
193
  # Remove finished jobs from the internal queue and launch as many as
194
- # possible respecting #maxjobs.
194
+ # possible respecting #maxjobs or #nodelist (if set).
195
195
  def flush!
196
196
  # Check for finished jobs
197
197
  @jobs_running.select! do |job|
@@ -209,17 +209,30 @@ class MiGA::Daemon < MiGA::MiGA
209
209
  # Avoid single datasets hogging resources
210
210
  @jobs_to_run.rotate! rand(jobs_to_run.size)
211
211
  # Launch as many +jobs_to_run+ as possible
212
- while jobs_running.size < maxjobs
212
+ while hostk = next_host
213
213
  break if jobs_to_run.empty?
214
- launch_job @jobs_to_run.shift
214
+ launch_job(@jobs_to_run.shift, hostk)
215
215
  end
216
216
  end
217
217
 
218
+ ##
219
+ # Retrieves the host index of an available node (if any), nil otherwise. If
220
+ # #nodelist is not set, returns true as long as #maxjobs is not reached
221
+ def next_host
222
+ if nodelist.nil?
223
+ return jobs_running.size < maxjobs
224
+ end
225
+ allk = (0 .. nodelist.size-1).to_a
226
+ busyk = jobs_running.map { |k| k[:hostk] }
227
+ availk = allk - busyk
228
+ availk.empty? ? nil : availk.first
229
+ end
230
+
218
231
  ##
219
232
  # Remove dead jobs.
220
233
  def purge!
221
234
  @jobs_running.select! do |job|
222
- `#{sprintf(runopts(:alive), job[:pid])}`.chomp.to_i == 1
235
+ `#{runopts(:alive).miga_variables(pid: job[:pid])}`.chomp.to_i == 1
223
236
  end
224
237
  end
225
238
 
@@ -270,14 +283,21 @@ class MiGA::Daemon < MiGA::MiGA
270
283
 
271
284
  private
272
285
 
273
- def launch_job(job)
286
+ def launch_job(job, hostk = nil)
274
287
  # Execute job
275
- if runopts(:type) == 'bash'
288
+ case runopts(:type)
289
+ when 'ssh'
290
+ # Remote job
291
+ job[:hostk] = hostk
292
+ job[:cmd] = job[:cmd].miga_variables(host: nodelist[hostk])
293
+ job[:pid] = spawn job[:cmd]
294
+ Process.detach job[:pid] unless [nil, '', 0].include?(job[:pid])
295
+ when 'bash'
276
296
  # Local job
277
297
  job[:pid] = spawn job[:cmd]
278
298
  Process.detach job[:pid] unless [nil, '', 0].include?(job[:pid])
279
299
  else
280
- # Schedule cluster job
300
+ # Schedule cluster job (qsub, msub, slurm)
281
301
  job[:pid] = `#{job[:cmd]}`.chomp
282
302
  end
283
303
 
@@ -288,7 +308,20 @@ class MiGA::Daemon < MiGA::MiGA
288
308
  say "Unsuccessful #{job[:task_name]}, rescheduling"
289
309
  else
290
310
  @jobs_running << job
291
- say "Spawned pid:#{job[:pid]} for #{job[:task_name]}"
311
+ say "Spawned pid:#{job[:pid]}#{
312
+ " to #{job[:hostk]}:#{nodelist[job[:hostk]]}" if job[:hostk]
313
+ } for #{job[:task_name]}"
292
314
  end
293
315
  end
316
+
317
+ def update_format_0
318
+ say 'Outdated daemon.json format, updating from version 0'
319
+ {
320
+ cmd: %w[script vars cpus log task_name],
321
+ var: %w[key value],
322
+ alive: %w[pid],
323
+ kill: %w[pid]
324
+ }.each { |k,v| runopts(k, sprintf(runopts(k), *v.map{ |i| "{{#{i}}}" })) }
325
+ runopts(:format_version, 1)
326
+ end
294
327
  end
@@ -1,6 +1,7 @@
1
1
 
2
2
  require 'daemons'
3
3
  require 'date'
4
+ require 'shellwords'
4
5
 
5
6
  class MiGA::Daemon < MiGA::MiGA
6
7
  end
@@ -14,10 +15,18 @@ module MiGA::Daemon::Base
14
15
  def runopts(k, v = nil, force = false)
15
16
  k = k.to_sym
16
17
  unless v.nil?
17
- if [:latency, :maxjobs, :ppn].include?(k)
18
+ case k
19
+ when :latency, :maxjobs, :ppn, :format_version
18
20
  v = v.to_i
19
- elsif [:shutdown_when_done].include?(k)
21
+ when :shutdown_when_done
20
22
  v = !!v
23
+ when :nodelist
24
+ if v =~ /^\$/
25
+ vv = ENV[v.sub('$','')] or raise "Unset environment variable: #{v}"
26
+ v = vv
27
+ end
28
+ say "Reading node list: #{v}"
29
+ v = File.readlines(v).map(&:chomp)
21
30
  end
22
31
  raise "Daemon's #{k} cannot be set to zero." if !force and v == 0
23
32
  @runopts[k] = v
@@ -26,32 +35,36 @@ module MiGA::Daemon::Base
26
35
  end
27
36
 
28
37
  ##
29
- # Returns Integer indicating the number of seconds to sleep between checks.
38
+ # Returns Integer indicating the number of seconds to sleep between checks
30
39
  def latency() runopts(:latency); end
31
40
 
32
41
  ##
33
- # Returns Integer indicating the maximum number of concurrent jobs to run.
42
+ # Returns Integer indicating the maximum number of concurrent jobs to run
34
43
  def maxjobs() runopts(:maxjobs); end
35
44
 
36
45
  ##
37
- # Returns Integer indicating the number of CPUs per job.
46
+ # Returns the path to the list of execution hostnames
47
+ def nodelist() runopts(:nodelist); end
48
+
49
+ ##
50
+ # Returns Integer indicating the number of CPUs per job
38
51
  def ppn() runopts(:ppn); end
39
52
 
40
53
  ##
41
54
  # Returns Boolean indicating if the daemon should shutdown when processing is
42
- # complete.
55
+ # complete
43
56
  def shutdown_when_done?() !!runopts(:shutdown_when_done); end
44
57
 
45
58
  ##
46
- # Initializes the daemon with +opts+.
59
+ # Initializes the daemon with +opts+
47
60
  def start(opts = []) daemon('start', opts); end
48
61
 
49
62
  ##
50
- # Stops the daemon with +opts+.
63
+ # Stops the daemon with +opts+
51
64
  def stop(opts = []) daemon('stop', opts); end
52
65
 
53
66
  ##
54
- # Restarts the daemon with +opts+.
67
+ # Restarts the daemon with +opts+
55
68
  def restart(opts = []) daemon('restart', opts); end
56
69
 
57
70
  ##
data/lib/miga/version.rb CHANGED
@@ -10,7 +10,7 @@ module MiGA
10
10
  # - Float representing the major.minor version.
11
11
  # - Integer representing gem releases of the current version.
12
12
  # - Integer representing minor changes that require new version number.
13
- VERSION = [0.5, 4, 0]
13
+ VERSION = [0.5, 5, 0]
14
14
 
15
15
  ##
16
16
  # Nickname for the current major.minor version.
@@ -18,7 +18,7 @@ module MiGA
18
18
 
19
19
  ##
20
20
  # Date of the current gem release.
21
- VERSION_DATE = Date.new(2020, 1, 22)
21
+ VERSION_DATE = Date.new(2020, 2, 4)
22
22
 
23
23
  ##
24
24
  # Reference of MiGA.
data/test/daemon_test.rb CHANGED
@@ -9,8 +9,9 @@ class DaemonTest < Test::Unit::TestCase
9
9
  ENV["MIGA_HOME"] = $tmp
10
10
  FileUtils.touch("#{ENV["MIGA_HOME"]}/.miga_rc")
11
11
  File.open("#{ENV["MIGA_HOME"]}/.miga_daemon.json", "w") do |fh|
12
- fh.puts '{"maxjobs":1,"ppn":1,"latency":2,"varsep":" ","var":"%s=%s",
13
- "cmd":"%5$s","alive":"echo 1 # %s","type":"bash"}'
12
+ fh.puts '{"maxjobs":1,"ppn":1,"latency":2,"varsep":" ",
13
+ "var":"{{key}}={{value}}","cmd":"{{task_name}}",
14
+ "alive":"echo 1 # {{pid}}","type":"bash","format_version":1}'
14
15
  end
15
16
  $p1 = MiGA::Project.new(File.expand_path("project1", $tmp))
16
17
  $d1 = MiGA::Daemon.new($p1)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: miga-base
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4.0
4
+ version: 0.5.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luis M. Rodriguez-R
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-22 00:00:00.000000000 Z
11
+ date: 2020-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: daemons