hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a190__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/__pyinstaller/hook-hpcflow.py +8 -6
- hpcflow/_version.py +1 -1
- hpcflow/app.py +1 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
- hpcflow/sdk/__init__.py +21 -15
- hpcflow/sdk/app.py +2133 -770
- hpcflow/sdk/cli.py +281 -250
- hpcflow/sdk/cli_common.py +6 -2
- hpcflow/sdk/config/__init__.py +1 -1
- hpcflow/sdk/config/callbacks.py +77 -42
- hpcflow/sdk/config/cli.py +126 -103
- hpcflow/sdk/config/config.py +578 -311
- hpcflow/sdk/config/config_file.py +131 -95
- hpcflow/sdk/config/errors.py +112 -85
- hpcflow/sdk/config/types.py +145 -0
- hpcflow/sdk/core/actions.py +1054 -994
- hpcflow/sdk/core/app_aware.py +24 -0
- hpcflow/sdk/core/cache.py +81 -63
- hpcflow/sdk/core/command_files.py +275 -185
- hpcflow/sdk/core/commands.py +111 -107
- hpcflow/sdk/core/element.py +724 -503
- hpcflow/sdk/core/enums.py +192 -0
- hpcflow/sdk/core/environment.py +74 -93
- hpcflow/sdk/core/errors.py +398 -51
- hpcflow/sdk/core/json_like.py +540 -272
- hpcflow/sdk/core/loop.py +380 -334
- hpcflow/sdk/core/loop_cache.py +160 -43
- hpcflow/sdk/core/object_list.py +370 -207
- hpcflow/sdk/core/parameters.py +728 -600
- hpcflow/sdk/core/rule.py +59 -41
- hpcflow/sdk/core/run_dir_files.py +33 -22
- hpcflow/sdk/core/task.py +1546 -1325
- hpcflow/sdk/core/task_schema.py +240 -196
- hpcflow/sdk/core/test_utils.py +126 -88
- hpcflow/sdk/core/types.py +387 -0
- hpcflow/sdk/core/utils.py +410 -305
- hpcflow/sdk/core/validation.py +82 -9
- hpcflow/sdk/core/workflow.py +1192 -1028
- hpcflow/sdk/core/zarr_io.py +98 -137
- hpcflow/sdk/demo/cli.py +46 -33
- hpcflow/sdk/helper/cli.py +18 -16
- hpcflow/sdk/helper/helper.py +75 -63
- hpcflow/sdk/helper/watcher.py +61 -28
- hpcflow/sdk/log.py +83 -59
- hpcflow/sdk/persistence/__init__.py +8 -31
- hpcflow/sdk/persistence/base.py +988 -586
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +38 -0
- hpcflow/sdk/persistence/json.py +408 -153
- hpcflow/sdk/persistence/pending.py +158 -123
- hpcflow/sdk/persistence/store_resource.py +37 -22
- hpcflow/sdk/persistence/types.py +307 -0
- hpcflow/sdk/persistence/utils.py +14 -11
- hpcflow/sdk/persistence/zarr.py +477 -420
- hpcflow/sdk/runtime.py +44 -41
- hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
- hpcflow/sdk/submission/jobscript.py +444 -404
- hpcflow/sdk/submission/schedulers/__init__.py +133 -40
- hpcflow/sdk/submission/schedulers/direct.py +97 -71
- hpcflow/sdk/submission/schedulers/sge.py +132 -126
- hpcflow/sdk/submission/schedulers/slurm.py +263 -268
- hpcflow/sdk/submission/schedulers/utils.py +7 -2
- hpcflow/sdk/submission/shells/__init__.py +14 -15
- hpcflow/sdk/submission/shells/base.py +102 -29
- hpcflow/sdk/submission/shells/bash.py +72 -55
- hpcflow/sdk/submission/shells/os_version.py +31 -30
- hpcflow/sdk/submission/shells/powershell.py +37 -29
- hpcflow/sdk/submission/submission.py +203 -257
- hpcflow/sdk/submission/types.py +143 -0
- hpcflow/sdk/typing.py +163 -12
- hpcflow/tests/conftest.py +8 -6
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
- hpcflow/tests/scripts/test_main_scripts.py +60 -30
- hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -4
- hpcflow/tests/unit/test_action.py +86 -75
- hpcflow/tests/unit/test_action_rule.py +9 -4
- hpcflow/tests/unit/test_app.py +13 -6
- hpcflow/tests/unit/test_cli.py +1 -1
- hpcflow/tests/unit/test_command.py +71 -54
- hpcflow/tests/unit/test_config.py +20 -15
- hpcflow/tests/unit/test_config_file.py +21 -18
- hpcflow/tests/unit/test_element.py +58 -62
- hpcflow/tests/unit/test_element_iteration.py +3 -1
- hpcflow/tests/unit/test_element_set.py +29 -19
- hpcflow/tests/unit/test_group.py +4 -2
- hpcflow/tests/unit/test_input_source.py +116 -93
- hpcflow/tests/unit/test_input_value.py +29 -24
- hpcflow/tests/unit/test_json_like.py +44 -35
- hpcflow/tests/unit/test_loop.py +65 -58
- hpcflow/tests/unit/test_object_list.py +17 -12
- hpcflow/tests/unit/test_parameter.py +16 -7
- hpcflow/tests/unit/test_persistence.py +48 -35
- hpcflow/tests/unit/test_resources.py +20 -18
- hpcflow/tests/unit/test_run.py +8 -3
- hpcflow/tests/unit/test_runtime.py +2 -1
- hpcflow/tests/unit/test_schema_input.py +23 -15
- hpcflow/tests/unit/test_shell.py +3 -2
- hpcflow/tests/unit/test_slurm.py +8 -7
- hpcflow/tests/unit/test_submission.py +39 -19
- hpcflow/tests/unit/test_task.py +352 -247
- hpcflow/tests/unit/test_task_schema.py +33 -20
- hpcflow/tests/unit/test_utils.py +9 -11
- hpcflow/tests/unit/test_value_sequence.py +15 -12
- hpcflow/tests/unit/test_workflow.py +114 -83
- hpcflow/tests/unit/test_workflow_template.py +0 -1
- hpcflow/tests/workflows/test_jobscript.py +2 -1
- hpcflow/tests/workflows/test_workflows.py +18 -13
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/METADATA +2 -1
- hpcflow_new2-0.2.0a190.dist-info/RECORD +165 -0
- hpcflow/sdk/core/parallel.py +0 -21
- hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
- {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
@@ -2,22 +2,34 @@
|
|
2
2
|
An interface to SGE.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from
|
5
|
+
from __future__ import annotations
|
6
|
+
from collections.abc import Sequence
|
6
7
|
import re
|
7
|
-
from typing import
|
8
|
+
from typing import TYPE_CHECKING
|
9
|
+
from typing_extensions import override
|
10
|
+
from hpcflow.sdk.typing import hydrate
|
8
11
|
from hpcflow.sdk.core.errors import (
|
9
12
|
IncompatibleSGEPEError,
|
10
13
|
NoCompatibleSGEPEError,
|
11
14
|
UnknownSGEPEError,
|
12
15
|
)
|
13
16
|
from hpcflow.sdk.log import TimeIt
|
14
|
-
from hpcflow.sdk.submission.
|
15
|
-
from hpcflow.sdk.submission.schedulers import
|
17
|
+
from hpcflow.sdk.submission.enums import JobscriptElementState
|
18
|
+
from hpcflow.sdk.submission.schedulers import QueuedScheduler
|
16
19
|
from hpcflow.sdk.submission.schedulers.utils import run_cmd
|
17
|
-
from hpcflow.sdk.submission.shells.base import Shell
|
18
20
|
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
from collections.abc import Iterator, Mapping
|
23
|
+
from typing import Any, ClassVar
|
24
|
+
from ...config.types import SchedulerConfigDescriptor
|
25
|
+
from ...core.element import ElementResources
|
26
|
+
from ..jobscript import Jobscript
|
27
|
+
from ..types import VersionInfo
|
28
|
+
from ..shells.base import Shell
|
19
29
|
|
20
|
-
|
30
|
+
|
31
|
+
@hydrate
|
32
|
+
class SGEPosix(QueuedScheduler):
|
21
33
|
"""
|
22
34
|
A scheduler that uses SGE.
|
23
35
|
|
@@ -43,36 +55,34 @@ class SGEPosix(Scheduler):
|
|
43
55
|
|
44
56
|
"""
|
45
57
|
|
46
|
-
_app_attr = "app"
|
47
|
-
|
48
58
|
#: Default args for shebang line.
|
49
|
-
DEFAULT_SHEBANG_ARGS = ""
|
59
|
+
DEFAULT_SHEBANG_ARGS: ClassVar[str] = ""
|
50
60
|
#: Default submission command.
|
51
|
-
DEFAULT_SUBMIT_CMD = "qsub"
|
61
|
+
DEFAULT_SUBMIT_CMD: ClassVar[str] = "qsub"
|
52
62
|
#: Default command to show the queue state.
|
53
|
-
DEFAULT_SHOW_CMD =
|
63
|
+
DEFAULT_SHOW_CMD: ClassVar[Sequence[str]] = ("qstat",)
|
54
64
|
#: Default cancel command.
|
55
|
-
DEFAULT_DEL_CMD = "qdel"
|
65
|
+
DEFAULT_DEL_CMD: ClassVar[str] = "qdel"
|
56
66
|
#: Default job control directive prefix.
|
57
|
-
DEFAULT_JS_CMD = "#$"
|
67
|
+
DEFAULT_JS_CMD: ClassVar[str] = "#$"
|
58
68
|
#: Default prefix to enable array processing.
|
59
|
-
DEFAULT_ARRAY_SWITCH = "-t"
|
69
|
+
DEFAULT_ARRAY_SWITCH: ClassVar[str] = "-t"
|
60
70
|
#: Default shell variable with array ID.
|
61
|
-
DEFAULT_ARRAY_ITEM_VAR = "SGE_TASK_ID"
|
71
|
+
DEFAULT_ARRAY_ITEM_VAR: ClassVar[str] = "SGE_TASK_ID"
|
62
72
|
#: Default switch to control CWD.
|
63
|
-
DEFAULT_CWD_SWITCH = "-cwd"
|
73
|
+
DEFAULT_CWD_SWITCH: ClassVar[str] = "-cwd"
|
64
74
|
#: Default command to get the login nodes.
|
65
|
-
DEFAULT_LOGIN_NODES_CMD =
|
75
|
+
DEFAULT_LOGIN_NODES_CMD: ClassVar[Sequence[str]] = ("qconf", "-sh")
|
66
76
|
|
67
77
|
#: Maps scheduler state codes to :py:class:`JobscriptElementState` values.
|
68
|
-
state_lookup = {
|
78
|
+
state_lookup: ClassVar[Mapping[str, JobscriptElementState]] = {
|
69
79
|
"qw": JobscriptElementState.pending,
|
70
80
|
"hq": JobscriptElementState.waiting,
|
71
81
|
"hR": JobscriptElementState.waiting,
|
72
82
|
"r": JobscriptElementState.running,
|
73
83
|
"t": JobscriptElementState.running,
|
74
84
|
"Rr": JobscriptElementState.running,
|
75
|
-
"Rt": JobscriptElementState.running,
|
85
|
+
# "Rt": JobscriptElementState.running,
|
76
86
|
"s": JobscriptElementState.errored,
|
77
87
|
"ts": JobscriptElementState.errored,
|
78
88
|
"S": JobscriptElementState.errored,
|
@@ -93,17 +103,22 @@ class SGEPosix(Scheduler):
|
|
93
103
|
"dT": JobscriptElementState.cancelled,
|
94
104
|
}
|
95
105
|
|
96
|
-
def __init__(self, cwd_switch=None, *args, **kwargs):
|
106
|
+
def __init__(self, cwd_switch: str | None = None, *args, **kwargs):
|
97
107
|
super().__init__(*args, **kwargs)
|
98
108
|
self.cwd_switch = cwd_switch or self.DEFAULT_CWD_SWITCH
|
99
109
|
|
100
110
|
@classmethod
|
111
|
+
@override
|
101
112
|
@TimeIt.decorator
|
102
|
-
def process_resources(
|
103
|
-
|
104
|
-
|
105
|
-
|
113
|
+
def process_resources(
|
114
|
+
cls, resources: ElementResources, scheduler_config: SchedulerConfigDescriptor
|
115
|
+
) -> None:
|
116
|
+
"""
|
117
|
+
Perform scheduler-specific processing to the element resources.
|
106
118
|
|
119
|
+
Note
|
120
|
+
----
|
121
|
+
This mutates `resources`.
|
107
122
|
"""
|
108
123
|
if resources.num_nodes is not None:
|
109
124
|
raise ValueError(
|
@@ -116,7 +131,7 @@ class SGEPosix(Scheduler):
|
|
116
131
|
if resources.SGE_parallel_env is not None:
|
117
132
|
# check user-specified `parallel_env` is valid and compatible with
|
118
133
|
# `num_cores`:
|
119
|
-
if resources.num_cores > 1:
|
134
|
+
if resources.num_cores and resources.num_cores > 1:
|
120
135
|
raise ValueError(
|
121
136
|
f"An SGE parallel environment should not be specified if `num_cores` "
|
122
137
|
f"is 1 (`SGE_parallel_env` was specified as "
|
@@ -126,105 +141,91 @@ class SGEPosix(Scheduler):
|
|
126
141
|
try:
|
127
142
|
env = para_envs[resources.SGE_parallel_env]
|
128
143
|
except KeyError:
|
129
|
-
raise UnknownSGEPEError(
|
130
|
-
f"The SGE parallel environment {resources.SGE_parallel_env!r} is not "
|
131
|
-
f"specified in the configuration. Specified parallel environments "
|
132
|
-
f"are {list(para_envs.keys())!r}."
|
133
|
-
)
|
144
|
+
raise UnknownSGEPEError(resources.SGE_parallel_env, para_envs)
|
134
145
|
if not cls.is_num_cores_supported(resources.num_cores, env["num_cores"]):
|
135
146
|
raise IncompatibleSGEPEError(
|
136
|
-
|
137
|
-
f"compatible with the number of cores requested: "
|
138
|
-
f"{resources.num_cores!r}."
|
147
|
+
resources.SGE_parallel_env, resources.num_cores
|
139
148
|
)
|
140
149
|
else:
|
141
150
|
# find the first compatible PE:
|
142
|
-
pe_match = -1 # pe_name might be `None`
|
143
151
|
for pe_name, pe_info in para_envs.items():
|
144
152
|
if cls.is_num_cores_supported(resources.num_cores, pe_info["num_cores"]):
|
145
|
-
|
153
|
+
resources.SGE_parallel_env = pe_name
|
146
154
|
break
|
147
|
-
if pe_match != -1:
|
148
|
-
resources.SGE_parallel_env = pe_name
|
149
155
|
else:
|
150
|
-
raise NoCompatibleSGEPEError(
|
151
|
-
f"No compatible SGE parallel environment could be found for the "
|
152
|
-
f"specified `num_cores` ({resources.num_cores!r})."
|
153
|
-
)
|
156
|
+
raise NoCompatibleSGEPEError(resources.num_cores)
|
154
157
|
|
155
|
-
def get_login_nodes(self):
|
158
|
+
def get_login_nodes(self) -> list[str]:
|
156
159
|
"""Return a list of hostnames of login/administrative nodes as reported by the
|
157
160
|
scheduler."""
|
158
|
-
|
161
|
+
get_login = self.login_nodes_cmd
|
162
|
+
assert get_login is not None and len(get_login) >= 1
|
163
|
+
stdout, stderr = run_cmd(get_login)
|
159
164
|
if stderr:
|
160
165
|
print(stderr)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
if resources.num_cores > 1:
|
167
|
-
lns.append(
|
168
|
-
f"{self.js_cmd} -pe {resources.SGE_parallel_env} {resources.num_cores}"
|
169
|
-
)
|
166
|
+
return stdout.strip().split("\n")
|
167
|
+
|
168
|
+
def __format_core_request_lines(self, resources: ElementResources) -> Iterator[str]:
|
169
|
+
if resources.num_cores and resources.num_cores > 1:
|
170
|
+
yield f"{self.js_cmd} -pe {resources.SGE_parallel_env} {resources.num_cores}"
|
170
171
|
if resources.max_array_items:
|
171
|
-
|
172
|
-
return lns
|
172
|
+
yield f"{self.js_cmd} -tc {resources.max_array_items}"
|
173
173
|
|
174
|
-
def
|
174
|
+
def __format_array_request(self, num_elements: int) -> str:
|
175
175
|
return f"{self.js_cmd} {self.array_switch} 1-{num_elements}"
|
176
176
|
|
177
|
-
def
|
177
|
+
def __format_std_stream_file_option_lines(
|
178
|
+
self, is_array: bool, sub_idx: int
|
179
|
+
) -> Iterator[str]:
|
178
180
|
# note: we can't modify the file names
|
179
|
-
|
180
|
-
|
181
|
-
f"{self.js_cmd} -o {base}",
|
182
|
-
f"{self.js_cmd} -e {base}",
|
183
|
-
]
|
181
|
+
yield f"{self.js_cmd} -o ./artifacts/submissions/{sub_idx}"
|
182
|
+
yield f"{self.js_cmd} -e ./artifacts/submissions/{sub_idx}"
|
184
183
|
|
185
|
-
|
184
|
+
@override
|
185
|
+
def format_options(
|
186
|
+
self, resources: ElementResources, num_elements: int, is_array: bool, sub_idx: int
|
187
|
+
) -> str:
|
186
188
|
"""
|
187
189
|
Format the options to the jobscript command.
|
188
190
|
"""
|
189
|
-
opts = []
|
191
|
+
opts: list[str] = []
|
190
192
|
opts.append(self.format_switch(self.cwd_switch))
|
191
|
-
opts.extend(self.
|
193
|
+
opts.extend(self.__format_core_request_lines(resources))
|
192
194
|
if is_array:
|
193
|
-
opts.append(self.
|
195
|
+
opts.append(self.__format_array_request(num_elements))
|
194
196
|
|
195
|
-
opts.extend(self.
|
197
|
+
opts.extend(self.__format_std_stream_file_option_lines(is_array, sub_idx))
|
196
198
|
|
197
199
|
for opt_k, opt_v in self.options.items():
|
198
|
-
if
|
199
|
-
|
200
|
-
|
200
|
+
if opt_v is None:
|
201
|
+
opts.append(f"{self.js_cmd} {opt_k}")
|
202
|
+
elif isinstance(opt_v, list):
|
203
|
+
opts.extend(f"{self.js_cmd} {opt_k} {i}" for i in opt_v)
|
201
204
|
elif opt_v:
|
202
205
|
opts.append(f"{self.js_cmd} {opt_k} {opt_v}")
|
203
|
-
elif opt_v is None:
|
204
|
-
opts.append(f"{self.js_cmd} {opt_k}")
|
205
206
|
|
206
207
|
return "\n".join(opts) + "\n"
|
207
208
|
|
209
|
+
@override
|
208
210
|
@TimeIt.decorator
|
209
|
-
def get_version_info(self):
|
210
|
-
|
211
|
-
stdout, stderr = run_cmd(vers_cmd)
|
211
|
+
def get_version_info(self) -> VersionInfo:
|
212
|
+
stdout, stderr = run_cmd([*self.show_cmd, "-help"])
|
212
213
|
if stderr:
|
213
214
|
print(stderr)
|
214
|
-
|
215
|
-
name, version =
|
216
|
-
|
215
|
+
first_line, *_ = stdout.split("\n")
|
216
|
+
name, version, *_ = first_line.strip().split()
|
217
|
+
return {
|
217
218
|
"scheduler_name": name,
|
218
219
|
"scheduler_version": version,
|
219
220
|
}
|
220
|
-
return out
|
221
221
|
|
222
|
+
@override
|
222
223
|
def get_submit_command(
|
223
224
|
self,
|
224
225
|
shell: Shell,
|
225
226
|
js_path: str,
|
226
|
-
deps:
|
227
|
-
) ->
|
227
|
+
deps: dict[Any, tuple[Any, ...]],
|
228
|
+
) -> list[str]:
|
228
229
|
"""
|
229
230
|
Get the command to use to submit a job to the scheduler.
|
230
231
|
|
@@ -234,8 +235,8 @@ class SGEPosix(Scheduler):
|
|
234
235
|
"""
|
235
236
|
cmd = [self.submit_cmd, "-terse"]
|
236
237
|
|
237
|
-
dep_job_IDs = []
|
238
|
-
dep_job_IDs_arr = []
|
238
|
+
dep_job_IDs: list[str] = []
|
239
|
+
dep_job_IDs_arr: list[str] = []
|
239
240
|
for job_ID, is_array_dep in deps.values():
|
240
241
|
if is_array_dep: # array dependency
|
241
242
|
dep_job_IDs_arr.append(str(job_ID))
|
@@ -253,60 +254,59 @@ class SGEPosix(Scheduler):
|
|
253
254
|
cmd.append(js_path)
|
254
255
|
return cmd
|
255
256
|
|
257
|
+
__SGE_JOB_ID_RE: ClassVar[re.Pattern] = re.compile(r"^\d+")
|
258
|
+
|
256
259
|
def parse_submission_output(self, stdout: str) -> str:
|
257
260
|
"""Extract scheduler reference for a newly submitted jobscript"""
|
258
|
-
match
|
259
|
-
if match:
|
260
|
-
job_ID = match.group()
|
261
|
-
else:
|
261
|
+
if not (match := self.__SGE_JOB_ID_RE.search(stdout)):
|
262
262
|
raise RuntimeError(f"Could not parse Job ID from scheduler output {stdout!r}")
|
263
|
-
return
|
263
|
+
return match.group()
|
264
264
|
|
265
|
-
def get_job_statuses(
|
265
|
+
def get_job_statuses(
|
266
|
+
self,
|
267
|
+
) -> Mapping[str, Mapping[int | None, JobscriptElementState]]:
|
266
268
|
"""Get information about all of this user's jobscripts that currently listed by
|
267
269
|
the scheduler."""
|
268
|
-
cmd = self.show_cmd
|
269
|
-
stdout, stderr = run_cmd(cmd, logger=self.
|
270
|
+
cmd = [*self.show_cmd, "-u", "$USER", "-g", "d"] # "-g d": separate arrays items
|
271
|
+
stdout, stderr = run_cmd(cmd, logger=self._app.submission_logger)
|
270
272
|
if stderr:
|
271
273
|
raise ValueError(
|
272
274
|
f"Could not get query SGE jobs. Command was: {cmd!r}; stderr was: "
|
273
275
|
f"{stderr}"
|
274
276
|
)
|
275
277
|
elif not stdout:
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
else
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
info[base_job_ID] = {}
|
303
|
-
|
304
|
-
info[base_job_ID][arr_idx] = state
|
278
|
+
return {}
|
279
|
+
|
280
|
+
info: dict[str, dict[int | None, JobscriptElementState]] = {}
|
281
|
+
lines = stdout.split("\n")
|
282
|
+
# assuming a job name with spaces means we can't split on spaces to get
|
283
|
+
# anywhere beyond the job name, so get the column index of the state heading
|
284
|
+
# and assume the state is always left-aligned with the heading:
|
285
|
+
state_idx = lines[0].index("state")
|
286
|
+
task_id_idx = lines[0].index("ja-task-ID")
|
287
|
+
for ln in lines[2:]:
|
288
|
+
if not ln:
|
289
|
+
continue
|
290
|
+
base_job_ID, *_ = ln.split()
|
291
|
+
|
292
|
+
# states can be one or two chars (for our limited purposes):
|
293
|
+
state_str = ln[state_idx : state_idx + 2].strip()
|
294
|
+
state = self.state_lookup[state_str]
|
295
|
+
|
296
|
+
arr_idx_s = ln[task_id_idx:].strip()
|
297
|
+
arr_idx = (
|
298
|
+
int(arr_idx_s) - 1 # We are using zero-indexed info
|
299
|
+
if arr_idx_s
|
300
|
+
else None
|
301
|
+
)
|
302
|
+
|
303
|
+
info.setdefault(base_job_ID, {})[arr_idx] = state
|
305
304
|
return info
|
306
305
|
|
306
|
+
@override
|
307
307
|
def get_job_state_info(
|
308
|
-
self, js_refs:
|
309
|
-
) ->
|
308
|
+
self, *, js_refs: Sequence[str] | None = None, num_js_elements: int = 0
|
309
|
+
) -> Mapping[str, Mapping[int | None, JobscriptElementState]]:
|
310
310
|
"""Query the scheduler to get the states of all of this user's jobs, optionally
|
311
311
|
filtering by specified job IDs.
|
312
312
|
|
@@ -316,23 +316,29 @@ class SGEPosix(Scheduler):
|
|
316
316
|
"""
|
317
317
|
info = self.get_job_statuses()
|
318
318
|
if js_refs:
|
319
|
-
|
319
|
+
return {k: v for k, v in info.items() if k in js_refs}
|
320
320
|
return info
|
321
321
|
|
322
|
-
|
322
|
+
@override
|
323
|
+
def cancel_jobs(
|
324
|
+
self,
|
325
|
+
js_refs: list[str],
|
326
|
+
jobscripts: list[Jobscript] | None = None,
|
327
|
+
num_js_elements: int = 0, # Ignored!
|
328
|
+
):
|
323
329
|
"""
|
324
330
|
Cancel submitted jobs.
|
325
331
|
"""
|
326
332
|
cmd = [self.del_cmd] + js_refs
|
327
|
-
self.
|
333
|
+
self._app.submission_logger.info(
|
328
334
|
f"cancelling {self.__class__.__name__} jobscripts with command: {cmd}."
|
329
335
|
)
|
330
|
-
stdout, stderr = run_cmd(cmd, logger=self.
|
336
|
+
stdout, stderr = run_cmd(cmd, logger=self._app.submission_logger)
|
331
337
|
if stderr:
|
332
338
|
raise ValueError(
|
333
339
|
f"Could not get query SGE {self.__class__.__name__}. Command was: "
|
334
340
|
f"{cmd!r}; stderr was: {stderr}"
|
335
341
|
)
|
336
|
-
self.
|
342
|
+
self._app.submission_logger.info(
|
337
343
|
f"jobscripts cancel command executed; stdout was: {stdout}."
|
338
344
|
)
|