hpcflow-new2 0.2.0a179__py3-none-any.whl → 0.2.0a180__py3-none-any.whl
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.
- hpcflow/_version.py +1 -1
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/sdk/__init__.py +4 -1
- hpcflow/sdk/app.py +160 -15
- hpcflow/sdk/cli.py +14 -0
- hpcflow/sdk/cli_common.py +83 -0
- hpcflow/sdk/config/__init__.py +4 -0
- hpcflow/sdk/config/callbacks.py +25 -2
- hpcflow/sdk/config/cli.py +4 -1
- hpcflow/sdk/config/config.py +188 -14
- hpcflow/sdk/config/config_file.py +91 -3
- hpcflow/sdk/config/errors.py +33 -0
- hpcflow/sdk/core/__init__.py +2 -0
- hpcflow/sdk/core/actions.py +492 -35
- hpcflow/sdk/core/cache.py +22 -0
- hpcflow/sdk/core/command_files.py +221 -5
- hpcflow/sdk/core/commands.py +57 -0
- hpcflow/sdk/core/element.py +407 -8
- hpcflow/sdk/core/environment.py +92 -0
- hpcflow/sdk/core/errors.py +245 -61
- hpcflow/sdk/core/json_like.py +72 -14
- hpcflow/sdk/core/loop.py +122 -21
- hpcflow/sdk/core/loop_cache.py +34 -9
- hpcflow/sdk/core/object_list.py +172 -26
- hpcflow/sdk/core/parallel.py +14 -0
- hpcflow/sdk/core/parameters.py +478 -25
- hpcflow/sdk/core/rule.py +31 -1
- hpcflow/sdk/core/run_dir_files.py +12 -2
- hpcflow/sdk/core/task.py +407 -80
- hpcflow/sdk/core/task_schema.py +70 -9
- hpcflow/sdk/core/test_utils.py +35 -0
- hpcflow/sdk/core/utils.py +101 -4
- hpcflow/sdk/core/validation.py +13 -1
- hpcflow/sdk/core/workflow.py +316 -96
- hpcflow/sdk/core/zarr_io.py +23 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +9 -0
- hpcflow/sdk/helper/helper.py +28 -0
- hpcflow/sdk/helper/watcher.py +33 -0
- hpcflow/sdk/log.py +40 -0
- hpcflow/sdk/persistence/__init__.py +14 -4
- hpcflow/sdk/persistence/base.py +289 -23
- hpcflow/sdk/persistence/json.py +29 -0
- hpcflow/sdk/persistence/pending.py +217 -107
- hpcflow/sdk/persistence/store_resource.py +58 -2
- hpcflow/sdk/persistence/utils.py +8 -0
- hpcflow/sdk/persistence/zarr.py +68 -1
- hpcflow/sdk/runtime.py +52 -10
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/jobscript.py +198 -9
- hpcflow/sdk/submission/jobscript_info.py +13 -0
- hpcflow/sdk/submission/schedulers/__init__.py +60 -0
- hpcflow/sdk/submission/schedulers/direct.py +53 -0
- hpcflow/sdk/submission/schedulers/sge.py +45 -7
- hpcflow/sdk/submission/schedulers/slurm.py +45 -8
- hpcflow/sdk/submission/schedulers/utils.py +4 -0
- hpcflow/sdk/submission/shells/__init__.py +11 -1
- hpcflow/sdk/submission/shells/base.py +32 -1
- hpcflow/sdk/submission/shells/bash.py +36 -1
- hpcflow/sdk/submission/shells/os_version.py +18 -6
- hpcflow/sdk/submission/shells/powershell.py +22 -0
- hpcflow/sdk/submission/submission.py +88 -3
- hpcflow/sdk/typing.py +10 -1
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/METADATA +1 -1
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/RECORD +70 -70
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a179.dist-info → hpcflow_new2-0.2.0a180.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
A direct job "scheduler" that just runs immediate subprocesses.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from pathlib import Path
|
2
6
|
import shutil
|
3
7
|
import signal
|
@@ -11,6 +15,22 @@ from hpcflow.sdk.submission.shells.base import Shell
|
|
11
15
|
|
12
16
|
|
13
17
|
class DirectScheduler(NullScheduler):
|
18
|
+
"""
|
19
|
+
A direct scheduler, that just runs jobs immediately as direct subprocesses.
|
20
|
+
|
21
|
+
The correct subclass (:py:class:`DirectPosix` or :py:class:`DirectWindows`) should
|
22
|
+
be used to create actual instances.
|
23
|
+
|
24
|
+
Keyword Args
|
25
|
+
------------
|
26
|
+
shell_args: str
|
27
|
+
Arguments to pass to the shell. Pre-quoted.
|
28
|
+
shebang_args: str
|
29
|
+
Arguments to set on the shebang line. Pre-quoted.
|
30
|
+
options: dict
|
31
|
+
Options to the jobscript command.
|
32
|
+
"""
|
33
|
+
|
14
34
|
def __init__(self, *args, **kwargs):
|
15
35
|
super().__init__(*args, **kwargs)
|
16
36
|
|
@@ -29,6 +49,9 @@ class DirectScheduler(NullScheduler):
|
|
29
49
|
js_path: str,
|
30
50
|
deps: List[Tuple],
|
31
51
|
) -> List[str]:
|
52
|
+
"""
|
53
|
+
Get the concrete submission command.
|
54
|
+
"""
|
32
55
|
return shell.get_direct_submit_command(js_path)
|
33
56
|
|
34
57
|
@staticmethod
|
@@ -104,6 +127,10 @@ class DirectScheduler(NullScheduler):
|
|
104
127
|
js_refs: List[Tuple[int, List[str]]],
|
105
128
|
jobscripts: List = None,
|
106
129
|
):
|
130
|
+
"""
|
131
|
+
Cancel some jobs.
|
132
|
+
"""
|
133
|
+
|
107
134
|
def callback(proc):
|
108
135
|
try:
|
109
136
|
js = js_proc_id[proc.pid]
|
@@ -145,7 +172,21 @@ class DirectScheduler(NullScheduler):
|
|
145
172
|
|
146
173
|
|
147
174
|
class DirectPosix(DirectScheduler):
|
175
|
+
"""
|
176
|
+
A direct scheduler for POSIX systems.
|
177
|
+
|
178
|
+
Keyword Args
|
179
|
+
------------
|
180
|
+
shell_args: str
|
181
|
+
Arguments to pass to the shell. Pre-quoted.
|
182
|
+
shebang_args: str
|
183
|
+
Arguments to set on the shebang line. Pre-quoted.
|
184
|
+
options: dict
|
185
|
+
Options to the jobscript command.
|
186
|
+
"""
|
187
|
+
|
148
188
|
_app_attr = "app"
|
189
|
+
#: Default shell.
|
149
190
|
DEFAULT_SHELL_EXECUTABLE = "/bin/bash"
|
150
191
|
|
151
192
|
def __init__(self, *args, **kwargs):
|
@@ -153,7 +194,19 @@ class DirectPosix(DirectScheduler):
|
|
153
194
|
|
154
195
|
|
155
196
|
class DirectWindows(DirectScheduler):
|
197
|
+
"""
|
198
|
+
A direct scheduler for Windows.
|
199
|
+
|
200
|
+
Keyword Args
|
201
|
+
------------
|
202
|
+
shell_args: str
|
203
|
+
Arguments to pass to the shell. Pre-quoted.
|
204
|
+
options: dict
|
205
|
+
Options to the jobscript command.
|
206
|
+
"""
|
207
|
+
|
156
208
|
_app_attr = "app"
|
209
|
+
#: Default shell.
|
157
210
|
DEFAULT_SHELL_EXECUTABLE = "powershell.exe"
|
158
211
|
|
159
212
|
def __init__(self, *args, **kwargs):
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
An interface to SGE.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from pathlib import Path
|
2
6
|
import re
|
3
7
|
from typing import Dict, List, Tuple
|
@@ -15,6 +19,18 @@ from hpcflow.sdk.submission.shells.base import Shell
|
|
15
19
|
|
16
20
|
class SGEPosix(Scheduler):
|
17
21
|
"""
|
22
|
+
A scheduler that uses SGE.
|
23
|
+
|
24
|
+
Keyword Args
|
25
|
+
------------
|
26
|
+
cwd_switch: str
|
27
|
+
Override of default switch to use to set the current working directory.
|
28
|
+
shell_args: str
|
29
|
+
Arguments to pass to the shell. Pre-quoted.
|
30
|
+
shebang_args: str
|
31
|
+
Arguments to set on the shebang line. Pre-quoted.
|
32
|
+
options: dict
|
33
|
+
Options to the jobscript command.
|
18
34
|
|
19
35
|
Notes
|
20
36
|
-----
|
@@ -29,17 +45,26 @@ class SGEPosix(Scheduler):
|
|
29
45
|
|
30
46
|
_app_attr = "app"
|
31
47
|
|
48
|
+
#: Default args for shebang line.
|
32
49
|
DEFAULT_SHEBANG_ARGS = ""
|
50
|
+
#: Default submission command.
|
33
51
|
DEFAULT_SUBMIT_CMD = "qsub"
|
52
|
+
#: Default command to show the queue state.
|
34
53
|
DEFAULT_SHOW_CMD = ["qstat"]
|
54
|
+
#: Default cancel command.
|
35
55
|
DEFAULT_DEL_CMD = "qdel"
|
56
|
+
#: Default job control directive prefix.
|
36
57
|
DEFAULT_JS_CMD = "#$"
|
58
|
+
#: Default prefix to enable array processing.
|
37
59
|
DEFAULT_ARRAY_SWITCH = "-t"
|
60
|
+
#: Default shell variable with array ID.
|
38
61
|
DEFAULT_ARRAY_ITEM_VAR = "SGE_TASK_ID"
|
62
|
+
#: Default switch to control CWD.
|
39
63
|
DEFAULT_CWD_SWITCH = "-cwd"
|
64
|
+
#: Default command to get the login nodes.
|
40
65
|
DEFAULT_LOGIN_NODES_CMD = ["qconf", "-sh"]
|
41
66
|
|
42
|
-
|
67
|
+
#: Maps scheduler state codes to :py:class:`JobscriptElementState` values.
|
43
68
|
state_lookup = {
|
44
69
|
"qw": JobscriptElementState.pending,
|
45
70
|
"hq": JobscriptElementState.waiting,
|
@@ -136,7 +161,7 @@ class SGEPosix(Scheduler):
|
|
136
161
|
nodes = stdout.strip().split("\n")
|
137
162
|
return nodes
|
138
163
|
|
139
|
-
def
|
164
|
+
def _format_core_request_lines(self, resources):
|
140
165
|
lns = []
|
141
166
|
if resources.num_cores > 1:
|
142
167
|
lns.append(
|
@@ -146,10 +171,10 @@ class SGEPosix(Scheduler):
|
|
146
171
|
lns.append(f"{self.js_cmd} -tc {resources.max_array_items}")
|
147
172
|
return lns
|
148
173
|
|
149
|
-
def
|
174
|
+
def _format_array_request(self, num_elements):
|
150
175
|
return f"{self.js_cmd} {self.array_switch} 1-{num_elements}"
|
151
176
|
|
152
|
-
def
|
177
|
+
def _format_std_stream_file_option_lines(self, is_array, sub_idx):
|
153
178
|
# note: we can't modify the file names
|
154
179
|
base = f"./artifacts/submissions/{sub_idx}"
|
155
180
|
return [
|
@@ -158,13 +183,16 @@ class SGEPosix(Scheduler):
|
|
158
183
|
]
|
159
184
|
|
160
185
|
def format_options(self, resources, num_elements, is_array, sub_idx):
|
186
|
+
"""
|
187
|
+
Format the options to the jobscript command.
|
188
|
+
"""
|
161
189
|
opts = []
|
162
190
|
opts.append(self.format_switch(self.cwd_switch))
|
163
|
-
opts.extend(self.
|
191
|
+
opts.extend(self._format_core_request_lines(resources))
|
164
192
|
if is_array:
|
165
|
-
opts.append(self.
|
193
|
+
opts.append(self._format_array_request(num_elements))
|
166
194
|
|
167
|
-
opts.extend(self.
|
195
|
+
opts.extend(self._format_std_stream_file_option_lines(is_array, sub_idx))
|
168
196
|
|
169
197
|
for opt_k, opt_v in self.options.items():
|
170
198
|
if isinstance(opt_v, list):
|
@@ -197,6 +225,13 @@ class SGEPosix(Scheduler):
|
|
197
225
|
js_path: str,
|
198
226
|
deps: List[Tuple],
|
199
227
|
) -> List[str]:
|
228
|
+
"""
|
229
|
+
Get the command to use to submit a job to the scheduler.
|
230
|
+
|
231
|
+
Returns
|
232
|
+
-------
|
233
|
+
List of argument words.
|
234
|
+
"""
|
200
235
|
cmd = [self.submit_cmd, "-terse"]
|
201
236
|
|
202
237
|
dep_job_IDs = []
|
@@ -285,6 +320,9 @@ class SGEPosix(Scheduler):
|
|
285
320
|
return info
|
286
321
|
|
287
322
|
def cancel_jobs(self, js_refs: List[str], jobscripts: List = None):
|
323
|
+
"""
|
324
|
+
Cancel submitted jobs.
|
325
|
+
"""
|
288
326
|
cmd = [self.del_cmd] + js_refs
|
289
327
|
self.app.submission_logger.info(
|
290
328
|
f"cancelling {self.__class__.__name__} jobscripts with command: {cmd}."
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
An interface to SLURM.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from pathlib import Path
|
2
6
|
import subprocess
|
3
7
|
import time
|
@@ -18,12 +22,24 @@ from hpcflow.sdk.submission.shells.base import Shell
|
|
18
22
|
|
19
23
|
class SlurmPosix(Scheduler):
|
20
24
|
"""
|
25
|
+
A scheduler that uses SLURM.
|
26
|
+
|
27
|
+
Keyword Args
|
28
|
+
------------
|
29
|
+
shell_args: str
|
30
|
+
Arguments to pass to the shell. Pre-quoted.
|
31
|
+
shebang_args: str
|
32
|
+
Arguments to set on the shebang line. Pre-quoted.
|
33
|
+
options: dict
|
34
|
+
Options to the jobscript command.
|
21
35
|
|
22
36
|
Notes
|
23
37
|
-----
|
24
38
|
- runs in current working directory by default [2]
|
25
39
|
|
26
|
-
|
40
|
+
Todo
|
41
|
+
----
|
42
|
+
- consider getting memory usage like: https://stackoverflow.com/a/44143229/5042280
|
27
43
|
|
28
44
|
References
|
29
45
|
----------
|
@@ -34,16 +50,24 @@ class SlurmPosix(Scheduler):
|
|
34
50
|
|
35
51
|
_app_attr = "app"
|
36
52
|
|
53
|
+
#: Default shell.
|
37
54
|
DEFAULT_SHELL_EXECUTABLE = "/bin/bash"
|
55
|
+
#: Default args for shebang line.
|
38
56
|
DEFAULT_SHEBANG_ARGS = ""
|
57
|
+
#: Default submission command.
|
39
58
|
DEFAULT_SUBMIT_CMD = "sbatch"
|
59
|
+
#: Default command to show the queue state.
|
40
60
|
DEFAULT_SHOW_CMD = ["squeue", "--me"]
|
61
|
+
#: Default cancel command.
|
41
62
|
DEFAULT_DEL_CMD = "scancel"
|
63
|
+
#: Default job control directive prefix.
|
42
64
|
DEFAULT_JS_CMD = "#SBATCH"
|
65
|
+
#: Default prefix to enable array processing.
|
43
66
|
DEFAULT_ARRAY_SWITCH = "--array"
|
67
|
+
#: Default shell variable with array ID.
|
44
68
|
DEFAULT_ARRAY_ITEM_VAR = "SLURM_ARRAY_TASK_ID"
|
45
69
|
|
46
|
-
|
70
|
+
#: Maps scheduler state codes to :py:class:`JobscriptElementState` values.
|
47
71
|
state_lookup = {
|
48
72
|
"PENDING": JobscriptElementState.pending,
|
49
73
|
"RUNNING": JobscriptElementState.running,
|
@@ -301,7 +325,7 @@ class SlurmPosix(Scheduler):
|
|
301
325
|
if part_match:
|
302
326
|
resources.SLURM_partition = part_match
|
303
327
|
|
304
|
-
def
|
328
|
+
def _format_core_request_lines(self, resources):
|
305
329
|
lns = []
|
306
330
|
if resources.SLURM_partition:
|
307
331
|
lns.append(f"{self.js_cmd} --partition {resources.SLURM_partition}")
|
@@ -324,13 +348,13 @@ class SlurmPosix(Scheduler):
|
|
324
348
|
|
325
349
|
return lns
|
326
350
|
|
327
|
-
def
|
351
|
+
def _format_array_request(self, num_elements, resources):
|
328
352
|
# TODO: Slurm docs start indices at zero, why are we starting at one?
|
329
353
|
# https://slurm.schedmd.com/sbatch.html#OPT_array
|
330
354
|
max_str = f"%{resources.max_array_items}" if resources.max_array_items else ""
|
331
355
|
return f"{self.js_cmd} {self.array_switch} 1-{num_elements}{max_str}"
|
332
356
|
|
333
|
-
def
|
357
|
+
def _format_std_stream_file_option_lines(self, is_array, sub_idx):
|
334
358
|
base = r"%x_"
|
335
359
|
if is_array:
|
336
360
|
base += r"%A.%a"
|
@@ -344,12 +368,15 @@ class SlurmPosix(Scheduler):
|
|
344
368
|
]
|
345
369
|
|
346
370
|
def format_options(self, resources, num_elements, is_array, sub_idx):
|
371
|
+
"""
|
372
|
+
Format the options to the scheduler.
|
373
|
+
"""
|
347
374
|
opts = []
|
348
|
-
opts.extend(self.
|
375
|
+
opts.extend(self._format_core_request_lines(resources))
|
349
376
|
if is_array:
|
350
|
-
opts.append(self.
|
377
|
+
opts.append(self._format_array_request(num_elements, resources))
|
351
378
|
|
352
|
-
opts.extend(self.
|
379
|
+
opts.extend(self._format_std_stream_file_option_lines(is_array, sub_idx))
|
353
380
|
|
354
381
|
for opt_k, opt_v in self.options.items():
|
355
382
|
if isinstance(opt_v, list):
|
@@ -387,6 +414,13 @@ class SlurmPosix(Scheduler):
|
|
387
414
|
js_path: str,
|
388
415
|
deps: List[Tuple],
|
389
416
|
) -> List[str]:
|
417
|
+
"""
|
418
|
+
Get the command to use to submit a job to the scheduler.
|
419
|
+
|
420
|
+
Returns
|
421
|
+
-------
|
422
|
+
List of argument words.
|
423
|
+
"""
|
390
424
|
cmd = [self.submit_cmd, "--parsable"]
|
391
425
|
|
392
426
|
dep_cmd = []
|
@@ -528,6 +562,9 @@ class SlurmPosix(Scheduler):
|
|
528
562
|
return info
|
529
563
|
|
530
564
|
def cancel_jobs(self, js_refs: List[str], jobscripts: List = None):
|
565
|
+
"""
|
566
|
+
Cancel submitted jobs.
|
567
|
+
"""
|
531
568
|
cmd = [self.del_cmd] + js_refs
|
532
569
|
self.app.submission_logger.info(
|
533
570
|
f"cancelling {self.__class__.__name__} jobscripts with command: {cmd}."
|
@@ -1,3 +1,6 @@
|
|
1
|
+
"""
|
2
|
+
Adapters for various shells.
|
3
|
+
"""
|
1
4
|
import os
|
2
5
|
from typing import Dict, Optional
|
3
6
|
|
@@ -7,6 +10,7 @@ from .base import Shell
|
|
7
10
|
from .bash import Bash, WSLBash
|
8
11
|
from .powershell import WindowsPowerShell
|
9
12
|
|
13
|
+
#: All supported shells.
|
10
14
|
ALL_SHELLS = {
|
11
15
|
"bash": {"posix": Bash},
|
12
16
|
"powershell": {"nt": WindowsPowerShell},
|
@@ -14,7 +18,7 @@ ALL_SHELLS = {
|
|
14
18
|
"wsl": {"nt": WSLBash}, # TODO: cast this to wsl+bash in ResourceSpec?
|
15
19
|
}
|
16
20
|
|
17
|
-
|
21
|
+
#: The default shell in the default config.
|
18
22
|
DEFAULT_SHELL_NAMES = {
|
19
23
|
"posix": "bash",
|
20
24
|
"nt": "powershell",
|
@@ -22,11 +26,17 @@ DEFAULT_SHELL_NAMES = {
|
|
22
26
|
|
23
27
|
|
24
28
|
def get_supported_shells(os_name: Optional[str] = None) -> Dict[str, Shell]:
|
29
|
+
"""
|
30
|
+
Get shells supported on the current or given OS.
|
31
|
+
"""
|
25
32
|
os_name = os_name or os.name
|
26
33
|
return {k: v.get(os_name) for k, v in ALL_SHELLS.items() if v.get(os_name)}
|
27
34
|
|
28
35
|
|
29
36
|
def get_shell(shell_name, os_name: Optional[str] = None, **kwargs) -> Shell:
|
37
|
+
"""
|
38
|
+
Get a shell interface with the given name for a given OS (or the current one).
|
39
|
+
"""
|
30
40
|
# TODO: apply config default shell args?
|
31
41
|
|
32
42
|
os_name = os_name or os.name
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
Base model of a shell.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from abc import ABC, abstractmethod
|
2
6
|
from pathlib import Path
|
3
7
|
from typing import Dict, List, Optional
|
@@ -10,6 +14,12 @@ class Shell(ABC):
|
|
10
14
|
bash on a POSIX OS, and provides snippets that are used to compose a jobscript for
|
11
15
|
that combination.
|
12
16
|
|
17
|
+
Parameters
|
18
|
+
----------
|
19
|
+
executable: str
|
20
|
+
Which executable implements the shell.
|
21
|
+
os_args:
|
22
|
+
Arguments to pass to the shell.
|
13
23
|
"""
|
14
24
|
|
15
25
|
def __init__(self, executable=None, os_args=None):
|
@@ -25,10 +35,16 @@ class Shell(ABC):
|
|
25
35
|
|
26
36
|
@property
|
27
37
|
def executable(self) -> List[str]:
|
38
|
+
"""
|
39
|
+
The executable to use plus any mandatory arguments.
|
40
|
+
"""
|
28
41
|
return [self._executable]
|
29
42
|
|
30
43
|
@property
|
31
|
-
def shebang_executable(self) ->
|
44
|
+
def shebang_executable(self) -> str:
|
45
|
+
"""
|
46
|
+
The executable to use in a shebang line.
|
47
|
+
"""
|
32
48
|
return self.executable
|
33
49
|
|
34
50
|
def get_direct_submit_command(self, js_path) -> List[str]:
|
@@ -40,6 +56,9 @@ class Shell(ABC):
|
|
40
56
|
"""Get shell and operating system information."""
|
41
57
|
|
42
58
|
def get_wait_command(self, workflow_app_alias: str, sub_idx: int, deps: Dict):
|
59
|
+
"""
|
60
|
+
Get the command to wait for a workflow.
|
61
|
+
"""
|
43
62
|
if deps:
|
44
63
|
return (
|
45
64
|
f'{workflow_app_alias} workflow $WK_PATH_ARG wait --jobscripts "{sub_idx}:'
|
@@ -51,9 +70,15 @@ class Shell(ABC):
|
|
51
70
|
|
52
71
|
@staticmethod
|
53
72
|
def process_app_invoc_executable(app_invoc_exe):
|
73
|
+
"""
|
74
|
+
Perform any post-processing of an application invocation command name.
|
75
|
+
"""
|
54
76
|
return app_invoc_exe
|
55
77
|
|
56
78
|
def process_JS_header_args(self, header_args: Dict) -> Dict:
|
79
|
+
"""
|
80
|
+
Process the application invocation key in the jobscript header arguments.
|
81
|
+
"""
|
57
82
|
app_invoc = self.process_app_invoc_executable(header_args["app_invoc"][0])
|
58
83
|
if len(header_args["app_invoc"]) > 1:
|
59
84
|
app_invoc += ' "' + header_args["app_invoc"][1] + '"'
|
@@ -62,7 +87,13 @@ class Shell(ABC):
|
|
62
87
|
return header_args
|
63
88
|
|
64
89
|
def prepare_JS_path(self, js_path: Path) -> str:
|
90
|
+
"""
|
91
|
+
Prepare the jobscript path for use.
|
92
|
+
"""
|
65
93
|
return str(js_path)
|
66
94
|
|
67
95
|
def prepare_element_run_dirs(self, run_dirs: List[List[Path]]) -> List[List[str]]:
|
96
|
+
"""
|
97
|
+
Prepare the element run directory names for use.
|
98
|
+
"""
|
68
99
|
return [[str(j) for j in i] for i in run_dirs]
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
Shell models based on the Bourne-Again Shell.
|
3
|
+
"""
|
4
|
+
|
1
5
|
from pathlib import Path
|
2
6
|
import subprocess
|
3
7
|
from textwrap import dedent, indent
|
@@ -11,14 +15,22 @@ from hpcflow.sdk.submission.shells.os_version import (
|
|
11
15
|
|
12
16
|
|
13
17
|
class Bash(Shell):
|
14
|
-
"""
|
18
|
+
"""
|
19
|
+
Class to represent using bash on a POSIX OS to generate and submit a jobscript.
|
20
|
+
"""
|
15
21
|
|
22
|
+
#: Default for executable name.
|
16
23
|
DEFAULT_EXE = "/bin/bash"
|
17
24
|
|
25
|
+
#: File extension for jobscripts.
|
18
26
|
JS_EXT = ".sh"
|
27
|
+
#: Basic indent.
|
19
28
|
JS_INDENT = " "
|
29
|
+
#: Indent for environment setup.
|
20
30
|
JS_ENV_SETUP_INDENT = 2 * JS_INDENT
|
31
|
+
#: Template for the jobscript shebang line.
|
21
32
|
JS_SHEBANG = """#!{shebang_executable} {shebang_args}"""
|
33
|
+
#: Template for the common part of the jobscript header.
|
22
34
|
JS_HEADER = dedent(
|
23
35
|
"""\
|
24
36
|
{workflow_app_alias} () {{
|
@@ -39,6 +51,7 @@ class Bash(Shell):
|
|
39
51
|
ELEM_RUN_DIR_FILE="$WK_PATH/artifacts/submissions/${{SUB_IDX}}/{element_run_dirs_file_path}"
|
40
52
|
"""
|
41
53
|
)
|
54
|
+
#: Template for the jobscript header when scheduled.
|
42
55
|
JS_SCHEDULER_HEADER = dedent(
|
43
56
|
"""\
|
44
57
|
{shebang}
|
@@ -47,6 +60,7 @@ class Bash(Shell):
|
|
47
60
|
{header}
|
48
61
|
"""
|
49
62
|
)
|
63
|
+
#: Template for the jobscript header when directly executed.
|
50
64
|
JS_DIRECT_HEADER = dedent(
|
51
65
|
"""\
|
52
66
|
{shebang}
|
@@ -55,6 +69,7 @@ class Bash(Shell):
|
|
55
69
|
{wait_command}
|
56
70
|
"""
|
57
71
|
)
|
72
|
+
#: Template for the jobscript body.
|
58
73
|
JS_MAIN = dedent(
|
59
74
|
"""\
|
60
75
|
elem_EAR_IDs=`sed "$((${{JS_elem_idx}} + 1))q;d" "$EAR_ID_FILE"`
|
@@ -103,6 +118,7 @@ class Bash(Shell):
|
|
103
118
|
done
|
104
119
|
"""
|
105
120
|
)
|
121
|
+
#: Template for the element processing loop in a jobscript.
|
106
122
|
JS_ELEMENT_LOOP = dedent(
|
107
123
|
"""\
|
108
124
|
for ((JS_elem_idx=0;JS_elem_idx<{num_elements};JS_elem_idx++))
|
@@ -112,6 +128,7 @@ class Bash(Shell):
|
|
112
128
|
cd "$WK_PATH"
|
113
129
|
"""
|
114
130
|
)
|
131
|
+
#: Template for the array handling code in a jobscript.
|
115
132
|
JS_ELEMENT_ARRAY = dedent(
|
116
133
|
"""\
|
117
134
|
JS_elem_idx=$(({scheduler_array_item_var} - 1))
|
@@ -125,6 +142,9 @@ class Bash(Shell):
|
|
125
142
|
|
126
143
|
@property
|
127
144
|
def linux_release_file(self):
|
145
|
+
"""
|
146
|
+
The name of the file describing the Linux version.
|
147
|
+
"""
|
128
148
|
return self.os_args["linux_release_file"]
|
129
149
|
|
130
150
|
def _get_OS_info_POSIX(self):
|
@@ -169,6 +189,9 @@ class Bash(Shell):
|
|
169
189
|
return app_invoc_exe
|
170
190
|
|
171
191
|
def format_stream_assignment(self, shell_var_name, command):
|
192
|
+
"""
|
193
|
+
Produce code to assign the output of the command to a shell variable.
|
194
|
+
"""
|
172
195
|
return f"{shell_var_name}=`{command}`"
|
173
196
|
|
174
197
|
def format_save_parameter(
|
@@ -180,6 +203,9 @@ class Bash(Shell):
|
|
180
203
|
cmd_idx: int,
|
181
204
|
stderr: bool,
|
182
205
|
):
|
206
|
+
"""
|
207
|
+
Produce code to save a parameter's value into the workflow persistent store.
|
208
|
+
"""
|
183
209
|
# TODO: quote shell_var_name as well? e.g. if it's a white-space delimited list?
|
184
210
|
# and test.
|
185
211
|
stderr_str = " --stderr" if stderr else ""
|
@@ -192,6 +218,9 @@ class Bash(Shell):
|
|
192
218
|
)
|
193
219
|
|
194
220
|
def format_loop_check(self, workflow_app_alias: str, loop_name: str, run_ID: int):
|
221
|
+
"""
|
222
|
+
Produce code to check the looping status of part of a workflow.
|
223
|
+
"""
|
195
224
|
return (
|
196
225
|
f"{workflow_app_alias} "
|
197
226
|
f'internal workflow "$WK_PATH_ARG" check-loop '
|
@@ -248,8 +277,14 @@ class Bash(Shell):
|
|
248
277
|
|
249
278
|
|
250
279
|
class WSLBash(Bash):
|
280
|
+
"""
|
281
|
+
A variant of bash that handles running under WSL on Windows.
|
282
|
+
"""
|
283
|
+
|
284
|
+
#: Default name of the WSL interface executable.
|
251
285
|
DEFAULT_WSL_EXE = "wsl"
|
252
286
|
|
287
|
+
#: Template for the common part of the jobscript header.
|
253
288
|
JS_HEADER = Bash.JS_HEADER.replace(
|
254
289
|
'WK_PATH_ARG="$WK_PATH"',
|
255
290
|
'WK_PATH_ARG=`wslpath -m "$WK_PATH"`',
|
@@ -1,3 +1,7 @@
|
|
1
|
+
"""
|
2
|
+
Operating system information discovery helpers.
|
3
|
+
"""
|
4
|
+
|
1
5
|
import os
|
2
6
|
import platform
|
3
7
|
import re
|
@@ -8,6 +12,9 @@ DEFAULT_LINUX_RELEASE_FILE = "/etc/os-release"
|
|
8
12
|
|
9
13
|
|
10
14
|
def get_OS_info() -> Dict:
|
15
|
+
"""
|
16
|
+
Get basic operating system version info.
|
17
|
+
"""
|
11
18
|
uname = platform.uname()
|
12
19
|
return {
|
13
20
|
"OS_name": uname.system,
|
@@ -17,6 +24,9 @@ def get_OS_info() -> Dict:
|
|
17
24
|
|
18
25
|
|
19
26
|
def get_OS_info_windows() -> Dict:
|
27
|
+
"""
|
28
|
+
Get operating system version info: Windows version.
|
29
|
+
"""
|
20
30
|
return get_OS_info()
|
21
31
|
|
22
32
|
|
@@ -26,15 +36,17 @@ def get_OS_info_POSIX(
|
|
26
36
|
linux_release_file: Optional[str] = None,
|
27
37
|
) -> Dict:
|
28
38
|
"""
|
39
|
+
Get operating system version info: POSIX version.
|
40
|
+
|
29
41
|
Parameters
|
30
42
|
----------
|
31
|
-
WSL_executable
|
43
|
+
WSL_executable:
|
32
44
|
Executable to run subprocess calls via WSL on Windows.
|
33
|
-
use_py
|
34
|
-
If True, use the
|
35
|
-
Otherwise use subprocess to call
|
36
|
-
info in WSL on Windows, since we need to call the WSL executable.
|
37
|
-
linux_release_file
|
45
|
+
use_py:
|
46
|
+
If True, use the :py:func:`platform.uname` Python function to get the OS
|
47
|
+
information. Otherwise use subprocess to call ``uname``. We set this to False
|
48
|
+
when getting OS info in WSL on Windows, since we need to call the WSL executable.
|
49
|
+
linux_release_file:
|
38
50
|
If on Linux, record the name and version fields from this file.
|
39
51
|
|
40
52
|
"""
|