fractal-server 1.4.9__py3-none-any.whl → 2.0.0a0__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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/models/__init__.py +4 -7
- fractal_server/app/models/linkuserproject.py +9 -0
- fractal_server/app/models/security.py +6 -0
- fractal_server/app/models/state.py +1 -1
- fractal_server/app/models/v1/__init__.py +10 -0
- fractal_server/app/models/{dataset.py → v1/dataset.py} +5 -5
- fractal_server/app/models/{job.py → v1/job.py} +5 -5
- fractal_server/app/models/{project.py → v1/project.py} +5 -5
- fractal_server/app/models/{task.py → v1/task.py} +7 -2
- fractal_server/app/models/{workflow.py → v1/workflow.py} +5 -5
- fractal_server/app/models/v2/__init__.py +20 -0
- fractal_server/app/models/v2/dataset.py +55 -0
- fractal_server/app/models/v2/job.py +51 -0
- fractal_server/app/models/v2/project.py +31 -0
- fractal_server/app/models/v2/task.py +93 -0
- fractal_server/app/models/v2/workflow.py +43 -0
- fractal_server/app/models/v2/workflowtask.py +90 -0
- fractal_server/app/routes/{admin.py → admin/v1.py} +42 -42
- fractal_server/app/routes/admin/v2.py +275 -0
- fractal_server/app/routes/api/v1/__init__.py +7 -7
- fractal_server/app/routes/api/v1/_aux_functions.py +2 -2
- fractal_server/app/routes/api/v1/dataset.py +44 -37
- fractal_server/app/routes/api/v1/job.py +12 -12
- fractal_server/app/routes/api/v1/project.py +23 -21
- fractal_server/app/routes/api/v1/task.py +24 -14
- fractal_server/app/routes/api/v1/task_collection.py +16 -14
- fractal_server/app/routes/api/v1/workflow.py +24 -24
- fractal_server/app/routes/api/v1/workflowtask.py +10 -10
- fractal_server/app/routes/api/v2/__init__.py +28 -0
- fractal_server/app/routes/api/v2/_aux_functions.py +497 -0
- fractal_server/app/routes/api/v2/apply.py +220 -0
- fractal_server/app/routes/api/v2/dataset.py +310 -0
- fractal_server/app/routes/api/v2/images.py +212 -0
- fractal_server/app/routes/api/v2/job.py +200 -0
- fractal_server/app/routes/api/v2/project.py +205 -0
- fractal_server/app/routes/api/v2/task.py +222 -0
- fractal_server/app/routes/api/v2/task_collection.py +229 -0
- fractal_server/app/routes/api/v2/workflow.py +398 -0
- fractal_server/app/routes/api/v2/workflowtask.py +269 -0
- fractal_server/app/routes/aux/_job.py +1 -1
- fractal_server/app/runner/async_wrap.py +27 -0
- fractal_server/app/runner/exceptions.py +129 -0
- fractal_server/app/runner/executors/local/__init__.py +3 -0
- fractal_server/app/runner/{_local → executors/local}/executor.py +2 -2
- fractal_server/app/runner/executors/slurm/__init__.py +3 -0
- fractal_server/app/runner/{_slurm → executors/slurm}/_batching.py +1 -1
- fractal_server/app/runner/executors/slurm/_check_jobs_status.py +72 -0
- fractal_server/app/runner/{_slurm → executors/slurm}/_executor_wait_thread.py +3 -4
- fractal_server/app/runner/{_slurm → executors/slurm}/_slurm_config.py +3 -152
- fractal_server/app/runner/{_slurm → executors/slurm}/_subprocess_run_as_user.py +1 -1
- fractal_server/app/runner/{_slurm → executors/slurm}/executor.py +9 -9
- fractal_server/app/runner/filenames.py +6 -0
- fractal_server/app/runner/set_start_and_last_task_index.py +39 -0
- fractal_server/app/runner/task_files.py +105 -0
- fractal_server/app/runner/{__init__.py → v1/__init__.py} +36 -49
- fractal_server/app/runner/{_common.py → v1/_common.py} +13 -120
- fractal_server/app/runner/{_local → v1/_local}/__init__.py +6 -6
- fractal_server/app/runner/{_local → v1/_local}/_local_config.py +6 -7
- fractal_server/app/runner/{_local → v1/_local}/_submit_setup.py +1 -5
- fractal_server/app/runner/v1/_slurm/__init__.py +310 -0
- fractal_server/app/runner/{_slurm → v1/_slurm}/_submit_setup.py +3 -9
- fractal_server/app/runner/v1/_slurm/get_slurm_config.py +163 -0
- fractal_server/app/runner/v1/common.py +117 -0
- fractal_server/app/runner/{handle_failed_job.py → v1/handle_failed_job.py} +8 -8
- fractal_server/app/runner/v2/__init__.py +337 -0
- fractal_server/app/runner/v2/_local/__init__.py +169 -0
- fractal_server/app/runner/v2/_local/_local_config.py +118 -0
- fractal_server/app/runner/v2/_local/_submit_setup.py +52 -0
- fractal_server/app/runner/v2/_slurm/__init__.py +157 -0
- fractal_server/app/runner/v2/_slurm/_submit_setup.py +83 -0
- fractal_server/app/runner/v2/_slurm/get_slurm_config.py +179 -0
- fractal_server/app/runner/v2/components.py +5 -0
- fractal_server/app/runner/v2/deduplicate_list.py +24 -0
- fractal_server/app/runner/v2/handle_failed_job.py +156 -0
- fractal_server/app/runner/v2/merge_outputs.py +41 -0
- fractal_server/app/runner/v2/runner.py +264 -0
- fractal_server/app/runner/v2/runner_functions.py +339 -0
- fractal_server/app/runner/v2/runner_functions_low_level.py +134 -0
- fractal_server/app/runner/v2/task_interface.py +43 -0
- fractal_server/app/runner/v2/v1_compat.py +21 -0
- fractal_server/app/schemas/__init__.py +4 -42
- fractal_server/app/schemas/v1/__init__.py +42 -0
- fractal_server/app/schemas/{applyworkflow.py → v1/applyworkflow.py} +18 -18
- fractal_server/app/schemas/{dataset.py → v1/dataset.py} +30 -30
- fractal_server/app/schemas/{dumps.py → v1/dumps.py} +8 -8
- fractal_server/app/schemas/{manifest.py → v1/manifest.py} +5 -5
- fractal_server/app/schemas/{project.py → v1/project.py} +9 -9
- fractal_server/app/schemas/{task.py → v1/task.py} +12 -12
- fractal_server/app/schemas/{task_collection.py → v1/task_collection.py} +7 -7
- fractal_server/app/schemas/{workflow.py → v1/workflow.py} +38 -38
- fractal_server/app/schemas/v2/__init__.py +34 -0
- fractal_server/app/schemas/v2/dataset.py +88 -0
- fractal_server/app/schemas/v2/dumps.py +87 -0
- fractal_server/app/schemas/v2/job.py +113 -0
- fractal_server/app/schemas/v2/manifest.py +109 -0
- fractal_server/app/schemas/v2/project.py +36 -0
- fractal_server/app/schemas/v2/task.py +121 -0
- fractal_server/app/schemas/v2/task_collection.py +105 -0
- fractal_server/app/schemas/v2/workflow.py +78 -0
- fractal_server/app/schemas/v2/workflowtask.py +118 -0
- fractal_server/config.py +5 -10
- fractal_server/images/__init__.py +50 -0
- fractal_server/images/tools.py +86 -0
- fractal_server/main.py +11 -3
- fractal_server/migrations/versions/4b35c5cefbe3_tmp_is_v2_compatible.py +39 -0
- fractal_server/migrations/versions/56af171b0159_v2.py +217 -0
- fractal_server/migrations/versions/876f28db9d4e_tmp_split_task_and_wftask_meta.py +68 -0
- fractal_server/migrations/versions/974c802f0dd0_tmp_workflowtaskv2_type_in_db.py +37 -0
- fractal_server/migrations/versions/9cd305cd6023_tmp_workflowtaskv2.py +40 -0
- fractal_server/migrations/versions/a6231ed6273c_tmp_args_schemas_in_taskv2.py +42 -0
- fractal_server/migrations/versions/b9e9eed9d442_tmp_taskv2_type.py +37 -0
- fractal_server/migrations/versions/e3e639454d4b_tmp_make_task_meta_non_optional.py +50 -0
- fractal_server/tasks/__init__.py +0 -5
- fractal_server/tasks/endpoint_operations.py +13 -19
- fractal_server/tasks/utils.py +35 -0
- fractal_server/tasks/{_TaskCollectPip.py → v1/_TaskCollectPip.py} +3 -3
- fractal_server/tasks/{background_operations.py → v1/background_operations.py} +18 -50
- fractal_server/tasks/v1/get_collection_data.py +14 -0
- fractal_server/tasks/v2/_TaskCollectPip.py +103 -0
- fractal_server/tasks/v2/background_operations.py +382 -0
- fractal_server/tasks/v2/get_collection_data.py +14 -0
- {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/METADATA +3 -4
- fractal_server-2.0.0a0.dist-info/RECORD +166 -0
- fractal_server/app/runner/_slurm/.gitignore +0 -2
- fractal_server/app/runner/_slurm/__init__.py +0 -150
- fractal_server/app/runner/common.py +0 -311
- fractal_server-1.4.9.dist-info/RECORD +0 -97
- /fractal_server/app/runner/{_slurm → executors/slurm}/remote.py +0 -0
- {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/LICENSE +0 -0
- {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/WHEEL +0 -0
- {fractal_server-1.4.9.dist-info → fractal_server-2.0.0a0.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,6 @@ import subprocess # nosec
|
|
11
11
|
import traceback
|
12
12
|
from concurrent.futures import Executor
|
13
13
|
from copy import deepcopy
|
14
|
-
from functools import lru_cache
|
15
14
|
from functools import partial
|
16
15
|
from pathlib import Path
|
17
16
|
from shlex import split as shlex_split
|
@@ -19,22 +18,19 @@ from typing import Any
|
|
19
18
|
from typing import Callable
|
20
19
|
from typing import Optional
|
21
20
|
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
25
|
-
from
|
26
|
-
from
|
27
|
-
from
|
28
|
-
from
|
29
|
-
from
|
21
|
+
from ....config import get_settings
|
22
|
+
from ....logger import get_logger
|
23
|
+
from ....syringe import Inject
|
24
|
+
from ...models import Task
|
25
|
+
from ...models import WorkflowTask
|
26
|
+
from ...schemas.v1 import WorkflowTaskStatusTypeV1
|
27
|
+
from ..exceptions import JobExecutionError
|
28
|
+
from ..exceptions import TaskExecutionError
|
30
29
|
from .common import TaskParameters
|
31
30
|
from .common import write_args_file
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
METADATA_FILENAME = "metadata.json"
|
36
|
-
SHUTDOWN_FILENAME = "shutdown"
|
37
|
-
WORKFLOW_LOG_FILENAME = "workflow.log"
|
31
|
+
from fractal_server.app.runner.filenames import HISTORY_FILENAME
|
32
|
+
from fractal_server.app.runner.filenames import METADATA_FILENAME
|
33
|
+
from fractal_server.app.runner.task_files import get_task_file_paths
|
38
34
|
|
39
35
|
|
40
36
|
def no_op_submit_setup_call(
|
@@ -42,7 +38,6 @@ def no_op_submit_setup_call(
|
|
42
38
|
wftask: WorkflowTask,
|
43
39
|
workflow_dir: Path,
|
44
40
|
workflow_dir_user: Path,
|
45
|
-
task_pars: TaskParameters,
|
46
41
|
) -> dict:
|
47
42
|
"""
|
48
43
|
Default (no-operation) interface of submit_setup_call.
|
@@ -50,14 +45,6 @@ def no_op_submit_setup_call(
|
|
50
45
|
return {}
|
51
46
|
|
52
47
|
|
53
|
-
def sanitize_component(value: str) -> str:
|
54
|
-
"""
|
55
|
-
Remove {" ", "/", "."} form a string, e.g. going from
|
56
|
-
'plate.zarr/B/03/0' to 'plate_zarr_B_03_0'.
|
57
|
-
"""
|
58
|
-
return value.replace(" ", "_").replace("/", "_").replace(".", "_")
|
59
|
-
|
60
|
-
|
61
48
|
def _task_needs_image_list(_task: Task) -> bool:
|
62
49
|
"""
|
63
50
|
Whether a task requires `metadata["image"]` in its `args.json` file.
|
@@ -78,98 +65,6 @@ def _task_needs_image_list(_task: Task) -> bool:
|
|
78
65
|
return False
|
79
66
|
|
80
67
|
|
81
|
-
class TaskFiles:
|
82
|
-
"""
|
83
|
-
Group all file paths pertaining to a task
|
84
|
-
|
85
|
-
Attributes:
|
86
|
-
workflow_dir:
|
87
|
-
Server-owned directory to store all task-execution-related relevant
|
88
|
-
files (inputs, outputs, errors, and all meta files related to the
|
89
|
-
job execution). Note: users cannot write directly to this folder.
|
90
|
-
workflow_dir_user:
|
91
|
-
User-side directory with the same scope as `workflow_dir`, and
|
92
|
-
where a user can write.
|
93
|
-
task_order:
|
94
|
-
Positional order of the task within a workflow.
|
95
|
-
component:
|
96
|
-
Specific component to run the task for (relevant for tasks that
|
97
|
-
will be executed in parallel over many components).
|
98
|
-
file_prefix:
|
99
|
-
Prefix for all task-related files.
|
100
|
-
args:
|
101
|
-
Path for input json file.
|
102
|
-
metadiff:
|
103
|
-
Path for output json file with metadata update.
|
104
|
-
out:
|
105
|
-
Path for task-execution stdout.
|
106
|
-
err:
|
107
|
-
Path for task-execution stderr.
|
108
|
-
"""
|
109
|
-
|
110
|
-
workflow_dir: Path
|
111
|
-
workflow_dir_user: Path
|
112
|
-
task_order: Optional[int] = None
|
113
|
-
component: Optional[str] = None
|
114
|
-
|
115
|
-
file_prefix: str
|
116
|
-
args: Path
|
117
|
-
out: Path
|
118
|
-
err: Path
|
119
|
-
metadiff: Path
|
120
|
-
|
121
|
-
def __init__(
|
122
|
-
self,
|
123
|
-
workflow_dir: Path,
|
124
|
-
workflow_dir_user: Path,
|
125
|
-
task_order: Optional[int] = None,
|
126
|
-
component: Optional[str] = None,
|
127
|
-
):
|
128
|
-
self.workflow_dir = workflow_dir
|
129
|
-
self.workflow_dir_user = workflow_dir_user
|
130
|
-
self.task_order = task_order
|
131
|
-
self.component = component
|
132
|
-
|
133
|
-
if self.component is not None:
|
134
|
-
component_safe = sanitize_component(str(self.component))
|
135
|
-
component_safe = f"_par_{component_safe}"
|
136
|
-
else:
|
137
|
-
component_safe = ""
|
138
|
-
|
139
|
-
if self.task_order is not None:
|
140
|
-
order = str(self.task_order)
|
141
|
-
else:
|
142
|
-
order = "task"
|
143
|
-
self.file_prefix = f"{order}{component_safe}"
|
144
|
-
self.args = self.workflow_dir_user / f"{self.file_prefix}.args.json"
|
145
|
-
self.out = self.workflow_dir_user / f"{self.file_prefix}.out"
|
146
|
-
self.err = self.workflow_dir_user / f"{self.file_prefix}.err"
|
147
|
-
self.metadiff = (
|
148
|
-
self.workflow_dir_user / f"{self.file_prefix}.metadiff.json"
|
149
|
-
)
|
150
|
-
|
151
|
-
|
152
|
-
@lru_cache()
|
153
|
-
def get_task_file_paths(
|
154
|
-
workflow_dir: Path,
|
155
|
-
workflow_dir_user: Path,
|
156
|
-
task_order: Optional[int] = None,
|
157
|
-
component: Optional[str] = None,
|
158
|
-
) -> TaskFiles:
|
159
|
-
"""
|
160
|
-
Return the corrisponding TaskFiles object
|
161
|
-
|
162
|
-
This function is mainly used as a cache to avoid instantiating needless
|
163
|
-
objects.
|
164
|
-
"""
|
165
|
-
return TaskFiles(
|
166
|
-
workflow_dir=workflow_dir,
|
167
|
-
workflow_dir_user=workflow_dir_user,
|
168
|
-
task_order=task_order,
|
169
|
-
component=component,
|
170
|
-
)
|
171
|
-
|
172
|
-
|
173
68
|
def _call_command_wrapper(cmd: str, stdout: Path, stderr: Path) -> None:
|
174
69
|
"""
|
175
70
|
Call a command and write its stdout and stderr to files
|
@@ -331,7 +226,7 @@ def call_single_task(
|
|
331
226
|
wftask_dump["task"] = wftask.task.model_dump()
|
332
227
|
new_history_item = dict(
|
333
228
|
workflowtask=wftask_dump,
|
334
|
-
status=
|
229
|
+
status=WorkflowTaskStatusTypeV1.DONE,
|
335
230
|
parallelization=None,
|
336
231
|
)
|
337
232
|
updated_history = task_pars.history.copy()
|
@@ -529,7 +424,6 @@ def call_parallel_task(
|
|
529
424
|
try:
|
530
425
|
extra_setup = submit_setup_call(
|
531
426
|
wftask=wftask,
|
532
|
-
task_pars=task_pars_depend,
|
533
427
|
workflow_dir=workflow_dir,
|
534
428
|
workflow_dir_user=workflow_dir_user,
|
535
429
|
)
|
@@ -592,7 +486,7 @@ def call_parallel_task(
|
|
592
486
|
wftask_dump["task"] = wftask.task.model_dump()
|
593
487
|
new_history_item = dict(
|
594
488
|
workflowtask=wftask_dump,
|
595
|
-
status=
|
489
|
+
status=WorkflowTaskStatusTypeV1.DONE,
|
596
490
|
parallelization=dict(
|
597
491
|
parallelization_level=wftask.parallelization_level,
|
598
492
|
component_list=component_list,
|
@@ -681,7 +575,6 @@ def execute_tasks(
|
|
681
575
|
try:
|
682
576
|
extra_setup = submit_setup_call(
|
683
577
|
wftask=this_wftask,
|
684
|
-
task_pars=current_task_pars,
|
685
578
|
workflow_dir=workflow_dir,
|
686
579
|
workflow_dir_user=workflow_dir_user,
|
687
580
|
)
|
@@ -23,13 +23,13 @@ from pathlib import Path
|
|
23
23
|
from typing import Any
|
24
24
|
from typing import Optional
|
25
25
|
|
26
|
-
from
|
27
|
-
from
|
28
|
-
from
|
29
|
-
from
|
30
|
-
from ..
|
26
|
+
from ....models import Workflow # FIXME: this is v1 specific
|
27
|
+
from ...async_wrap import async_wrap
|
28
|
+
from ...executors.local.executor import FractalThreadPoolExecutor
|
29
|
+
from ...set_start_and_last_task_index import set_start_and_last_task_index
|
30
|
+
from .._common import execute_tasks # FIXME: this is v1 specific
|
31
|
+
from ..common import TaskParameters # FIXME: this is v1 specific
|
31
32
|
from ._submit_setup import _local_submit_setup
|
32
|
-
from .executor import FractalThreadPoolExecutor
|
33
33
|
|
34
34
|
|
35
35
|
def _process_workflow(
|
@@ -19,9 +19,9 @@ from pydantic import BaseModel
|
|
19
19
|
from pydantic import Extra
|
20
20
|
from pydantic.error_wrappers import ValidationError
|
21
21
|
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
22
|
+
from .....config import get_settings
|
23
|
+
from .....syringe import Inject
|
24
|
+
from ....models.v1 import WorkflowTask
|
25
25
|
|
26
26
|
|
27
27
|
class LocalBackendConfigError(ValueError):
|
@@ -63,15 +63,14 @@ def get_local_backend_config(
|
|
63
63
|
The sources for `parallel_tasks_per_job` attributes, starting from the
|
64
64
|
highest-priority one, are
|
65
65
|
|
66
|
-
1. Properties in `wftask.meta
|
67
|
-
`Workflow.insert_task`, also includes `wftask.task.meta`);
|
66
|
+
1. Properties in `wftask.meta`;
|
68
67
|
2. The general content of the local-backend configuration file;
|
69
68
|
3. The default value (`None`).
|
70
69
|
|
71
70
|
Arguments:
|
72
71
|
wftask:
|
73
|
-
WorkflowTask for which the backend configuration
|
74
|
-
prepared.
|
72
|
+
WorkflowTask (V1) for which the backend configuration should
|
73
|
+
be prepared.
|
75
74
|
config_path:
|
76
75
|
Path of local-backend configuration file; if `None`, use
|
77
76
|
`FRACTAL_LOCAL_CONFIG_FILE` variable from settings.
|
@@ -14,8 +14,7 @@ Submodule to define _local_submit_setup
|
|
14
14
|
from pathlib import Path
|
15
15
|
from typing import Optional
|
16
16
|
|
17
|
-
from
|
18
|
-
from ..common import TaskParameters
|
17
|
+
from ....models.v1 import WorkflowTask
|
19
18
|
from ._local_config import get_local_backend_config
|
20
19
|
|
21
20
|
|
@@ -24,7 +23,6 @@ def _local_submit_setup(
|
|
24
23
|
wftask: WorkflowTask,
|
25
24
|
workflow_dir: Optional[Path] = None,
|
26
25
|
workflow_dir_user: Optional[Path] = None,
|
27
|
-
task_pars: Optional[TaskParameters] = None,
|
28
26
|
) -> dict[str, object]:
|
29
27
|
"""
|
30
28
|
Collect WorfklowTask-specific configuration parameters from different
|
@@ -33,8 +31,6 @@ def _local_submit_setup(
|
|
33
31
|
Arguments:
|
34
32
|
wftask:
|
35
33
|
WorkflowTask for which the configuration is to be assembled
|
36
|
-
task_pars:
|
37
|
-
Not used in this function.
|
38
34
|
workflow_dir:
|
39
35
|
Not used in this function.
|
40
36
|
workflow_dir_user:
|
@@ -0,0 +1,310 @@
|
|
1
|
+
# Copyright 2022 (C) Friedrich Miescher Institute for Biomedical Research and
|
2
|
+
# University of Zurich
|
3
|
+
#
|
4
|
+
# Original authors:
|
5
|
+
# Jacopo Nespolo <jacopo.nespolo@exact-lab.it>
|
6
|
+
# Tommaso Comparin <tommaso.comparin@exact-lab.it>
|
7
|
+
# Marco Franzon <marco.franzon@exact-lab.it>
|
8
|
+
#
|
9
|
+
# This file is part of Fractal and was originally developed by eXact lab S.r.l.
|
10
|
+
# <exact-lab.it> under contract with Liberali Lab from the Friedrich Miescher
|
11
|
+
# Institute for Biomedical Research and Pelkmans Lab from the University of
|
12
|
+
# Zurich.
|
13
|
+
"""
|
14
|
+
Slurm Bakend
|
15
|
+
|
16
|
+
This backend runs fractal workflows in a SLURM cluster using Clusterfutures
|
17
|
+
Executor objects.
|
18
|
+
"""
|
19
|
+
from pathlib import Path
|
20
|
+
from typing import Any
|
21
|
+
from typing import Optional
|
22
|
+
from typing import Union
|
23
|
+
|
24
|
+
from ...async_wrap import async_wrap
|
25
|
+
from ...executors.slurm.executor import FractalSlurmExecutor
|
26
|
+
from ...set_start_and_last_task_index import set_start_and_last_task_index
|
27
|
+
from .._common import execute_tasks
|
28
|
+
from ..common import TaskParameters
|
29
|
+
from ._submit_setup import _slurm_submit_setup
|
30
|
+
from fractal_server.app.models.v1 import Workflow
|
31
|
+
from fractal_server.app.models.v1 import WorkflowTask
|
32
|
+
from fractal_server.app.runner.executors.slurm._slurm_config import (
|
33
|
+
_parse_mem_value,
|
34
|
+
)
|
35
|
+
from fractal_server.app.runner.executors.slurm._slurm_config import (
|
36
|
+
load_slurm_config_file,
|
37
|
+
)
|
38
|
+
from fractal_server.app.runner.executors.slurm._slurm_config import logger
|
39
|
+
from fractal_server.app.runner.executors.slurm._slurm_config import SlurmConfig
|
40
|
+
from fractal_server.app.runner.executors.slurm._slurm_config import (
|
41
|
+
SlurmConfigError,
|
42
|
+
)
|
43
|
+
|
44
|
+
|
45
|
+
def _process_workflow(
|
46
|
+
*,
|
47
|
+
workflow: Workflow,
|
48
|
+
input_paths: list[Path],
|
49
|
+
output_path: Path,
|
50
|
+
input_metadata: dict[str, Any],
|
51
|
+
input_history: list[dict[str, Any]],
|
52
|
+
logger_name: str,
|
53
|
+
workflow_dir: Path,
|
54
|
+
workflow_dir_user: Path,
|
55
|
+
first_task_index: int,
|
56
|
+
last_task_index: int,
|
57
|
+
slurm_user: Optional[str] = None,
|
58
|
+
slurm_account: Optional[str] = None,
|
59
|
+
user_cache_dir: str,
|
60
|
+
worker_init: Optional[Union[str, list[str]]] = None,
|
61
|
+
) -> dict[str, Any]:
|
62
|
+
"""
|
63
|
+
Internal processing routine for the SLURM backend
|
64
|
+
|
65
|
+
This function initialises the a FractalSlurmExecutor, setting logging,
|
66
|
+
workflow working dir and user to impersonate. It then schedules the
|
67
|
+
workflow tasks and returns the output dataset metadata.
|
68
|
+
|
69
|
+
Cf. [process_workflow][fractal_server.app.runner._local.process_workflow]
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
output_dataset_metadata: Metadata of the output dataset
|
73
|
+
"""
|
74
|
+
|
75
|
+
if not slurm_user:
|
76
|
+
raise RuntimeError(
|
77
|
+
"slurm_user argument is required, for slurm backend"
|
78
|
+
)
|
79
|
+
|
80
|
+
if isinstance(worker_init, str):
|
81
|
+
worker_init = worker_init.split("\n")
|
82
|
+
|
83
|
+
with FractalSlurmExecutor(
|
84
|
+
debug=True,
|
85
|
+
keep_logs=True,
|
86
|
+
slurm_user=slurm_user,
|
87
|
+
user_cache_dir=user_cache_dir,
|
88
|
+
working_dir=workflow_dir,
|
89
|
+
working_dir_user=workflow_dir_user,
|
90
|
+
common_script_lines=worker_init,
|
91
|
+
slurm_account=slurm_account,
|
92
|
+
) as executor:
|
93
|
+
output_task_pars = execute_tasks(
|
94
|
+
executor=executor,
|
95
|
+
task_list=workflow.task_list[
|
96
|
+
first_task_index : (last_task_index + 1) # noqa
|
97
|
+
], # noqa
|
98
|
+
task_pars=TaskParameters(
|
99
|
+
input_paths=input_paths,
|
100
|
+
output_path=output_path,
|
101
|
+
metadata=input_metadata,
|
102
|
+
history=input_history,
|
103
|
+
),
|
104
|
+
workflow_dir=workflow_dir,
|
105
|
+
workflow_dir_user=workflow_dir_user,
|
106
|
+
submit_setup_call=_slurm_submit_setup,
|
107
|
+
logger_name=logger_name,
|
108
|
+
)
|
109
|
+
output_dataset_metadata_history = dict(
|
110
|
+
metadata=output_task_pars.metadata, history=output_task_pars.history
|
111
|
+
)
|
112
|
+
return output_dataset_metadata_history
|
113
|
+
|
114
|
+
|
115
|
+
async def process_workflow(
|
116
|
+
*,
|
117
|
+
workflow: Workflow,
|
118
|
+
input_paths: list[Path],
|
119
|
+
output_path: Path,
|
120
|
+
input_metadata: dict[str, Any],
|
121
|
+
input_history: list[dict[str, Any]],
|
122
|
+
logger_name: str,
|
123
|
+
workflow_dir: Path,
|
124
|
+
workflow_dir_user: Optional[Path] = None,
|
125
|
+
user_cache_dir: Optional[str] = None,
|
126
|
+
slurm_user: Optional[str] = None,
|
127
|
+
slurm_account: Optional[str] = None,
|
128
|
+
worker_init: Optional[str] = None,
|
129
|
+
first_task_index: Optional[int] = None,
|
130
|
+
last_task_index: Optional[int] = None,
|
131
|
+
) -> dict[str, Any]:
|
132
|
+
"""
|
133
|
+
Process workflow (SLURM backend public interface)
|
134
|
+
|
135
|
+
Cf. [process_workflow][fractal_server.app.runner._local.process_workflow]
|
136
|
+
"""
|
137
|
+
|
138
|
+
# Set values of first_task_index and last_task_index
|
139
|
+
num_tasks = len(workflow.task_list)
|
140
|
+
first_task_index, last_task_index = set_start_and_last_task_index(
|
141
|
+
num_tasks,
|
142
|
+
first_task_index=first_task_index,
|
143
|
+
last_task_index=last_task_index,
|
144
|
+
)
|
145
|
+
|
146
|
+
output_dataset_metadata_history = await async_wrap(_process_workflow)(
|
147
|
+
workflow=workflow,
|
148
|
+
input_paths=input_paths,
|
149
|
+
output_path=output_path,
|
150
|
+
input_metadata=input_metadata,
|
151
|
+
input_history=input_history,
|
152
|
+
logger_name=logger_name,
|
153
|
+
workflow_dir=workflow_dir,
|
154
|
+
workflow_dir_user=workflow_dir_user,
|
155
|
+
slurm_user=slurm_user,
|
156
|
+
slurm_account=slurm_account,
|
157
|
+
user_cache_dir=user_cache_dir,
|
158
|
+
worker_init=worker_init,
|
159
|
+
first_task_index=first_task_index,
|
160
|
+
last_task_index=last_task_index,
|
161
|
+
)
|
162
|
+
return output_dataset_metadata_history
|
163
|
+
|
164
|
+
|
165
|
+
def get_slurm_config(
|
166
|
+
wftask: WorkflowTask,
|
167
|
+
workflow_dir: Path,
|
168
|
+
workflow_dir_user: Path,
|
169
|
+
config_path: Optional[Path] = None,
|
170
|
+
) -> SlurmConfig:
|
171
|
+
"""
|
172
|
+
Prepare a `SlurmConfig` configuration object
|
173
|
+
|
174
|
+
The sources for `SlurmConfig` attributes, in increasing priority order, are
|
175
|
+
|
176
|
+
1. The general content of the Fractal SLURM configuration file.
|
177
|
+
2. The GPU-specific content of the Fractal SLURM configuration file, if
|
178
|
+
appropriate.
|
179
|
+
3. Properties in `wftask.meta` (which, for `WorkflowTask`s added through
|
180
|
+
`Workflow.insert_task`, also includes `wftask.task.meta`);
|
181
|
+
|
182
|
+
Note: `wftask.meta` may be `None`.
|
183
|
+
|
184
|
+
Arguments:
|
185
|
+
wftask:
|
186
|
+
WorkflowTask for which the SLURM configuration is is to be
|
187
|
+
prepared.
|
188
|
+
workflow_dir:
|
189
|
+
Server-owned directory to store all task-execution-related relevant
|
190
|
+
files (inputs, outputs, errors, and all meta files related to the
|
191
|
+
job execution). Note: users cannot write directly to this folder.
|
192
|
+
workflow_dir_user:
|
193
|
+
User-side directory with the same scope as `workflow_dir`, and
|
194
|
+
where a user can write.
|
195
|
+
config_path:
|
196
|
+
Path of aFractal SLURM configuration file; if `None`, use
|
197
|
+
`FRACTAL_SLURM_CONFIG_FILE` variable from settings.
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
slurm_config:
|
201
|
+
The SlurmConfig object
|
202
|
+
"""
|
203
|
+
|
204
|
+
logger.debug(
|
205
|
+
"[get_slurm_config] WorkflowTask meta attribute: {wftask.meta=}"
|
206
|
+
)
|
207
|
+
|
208
|
+
# Incorporate slurm_env.default_slurm_config
|
209
|
+
slurm_env = load_slurm_config_file(config_path=config_path)
|
210
|
+
slurm_dict = slurm_env.default_slurm_config.dict(
|
211
|
+
exclude_unset=True, exclude={"mem"}
|
212
|
+
)
|
213
|
+
if slurm_env.default_slurm_config.mem:
|
214
|
+
slurm_dict["mem_per_task_MB"] = slurm_env.default_slurm_config.mem
|
215
|
+
|
216
|
+
# Incorporate slurm_env.batching_config
|
217
|
+
for key, value in slurm_env.batching_config.dict().items():
|
218
|
+
slurm_dict[key] = value
|
219
|
+
|
220
|
+
# Incorporate slurm_env.user_local_exports
|
221
|
+
slurm_dict["user_local_exports"] = slurm_env.user_local_exports
|
222
|
+
|
223
|
+
logger.debug(
|
224
|
+
"[get_slurm_config] Fractal SLURM configuration file: "
|
225
|
+
f"{slurm_env.dict()=}"
|
226
|
+
)
|
227
|
+
|
228
|
+
# GPU-related options
|
229
|
+
# Notes about priority:
|
230
|
+
# 1. This block of definitions takes priority over other definitions from
|
231
|
+
# slurm_env which are not under the `needs_gpu` subgroup
|
232
|
+
# 2. This block of definitions has lower priority than whatever comes next
|
233
|
+
# (i.e. from WorkflowTask.meta).
|
234
|
+
if wftask.meta is not None:
|
235
|
+
needs_gpu = wftask.meta.get("needs_gpu", False)
|
236
|
+
else:
|
237
|
+
needs_gpu = False
|
238
|
+
logger.debug(f"[get_slurm_config] {needs_gpu=}")
|
239
|
+
if needs_gpu:
|
240
|
+
for key, value in slurm_env.gpu_slurm_config.dict(
|
241
|
+
exclude_unset=True, exclude={"mem"}
|
242
|
+
).items():
|
243
|
+
slurm_dict[key] = value
|
244
|
+
if slurm_env.gpu_slurm_config.mem:
|
245
|
+
slurm_dict["mem_per_task_MB"] = slurm_env.gpu_slurm_config.mem
|
246
|
+
|
247
|
+
# Number of CPUs per task, for multithreading
|
248
|
+
if wftask.meta is not None and "cpus_per_task" in wftask.meta:
|
249
|
+
cpus_per_task = int(wftask.meta["cpus_per_task"])
|
250
|
+
slurm_dict["cpus_per_task"] = cpus_per_task
|
251
|
+
|
252
|
+
# Required memory per task, in MB
|
253
|
+
if wftask.meta is not None and "mem" in wftask.meta:
|
254
|
+
raw_mem = wftask.meta["mem"]
|
255
|
+
mem_per_task_MB = _parse_mem_value(raw_mem)
|
256
|
+
slurm_dict["mem_per_task_MB"] = mem_per_task_MB
|
257
|
+
|
258
|
+
# Job name
|
259
|
+
job_name = wftask.task.name.replace(" ", "_")
|
260
|
+
slurm_dict["job_name"] = job_name
|
261
|
+
|
262
|
+
# Optional SLURM arguments and extra lines
|
263
|
+
if wftask.meta is not None:
|
264
|
+
account = wftask.meta.get("account", None)
|
265
|
+
if account is not None:
|
266
|
+
error_msg = (
|
267
|
+
f"Invalid {account=} property in WorkflowTask `meta` "
|
268
|
+
"attribute.\n"
|
269
|
+
"SLURM account must be set in the request body of the "
|
270
|
+
"apply-workflow endpoint, or by modifying the user properties."
|
271
|
+
)
|
272
|
+
logger.error(error_msg)
|
273
|
+
raise SlurmConfigError(error_msg)
|
274
|
+
for key in ["time", "gres", "constraint"]:
|
275
|
+
value = wftask.meta.get(key, None)
|
276
|
+
if value:
|
277
|
+
slurm_dict[key] = value
|
278
|
+
if wftask.meta is not None:
|
279
|
+
extra_lines = wftask.meta.get("extra_lines", [])
|
280
|
+
else:
|
281
|
+
extra_lines = []
|
282
|
+
extra_lines = slurm_dict.get("extra_lines", []) + extra_lines
|
283
|
+
if len(set(extra_lines)) != len(extra_lines):
|
284
|
+
logger.debug(
|
285
|
+
"[get_slurm_config] Removing repeated elements "
|
286
|
+
f"from {extra_lines=}."
|
287
|
+
)
|
288
|
+
extra_lines = list(set(extra_lines))
|
289
|
+
slurm_dict["extra_lines"] = extra_lines
|
290
|
+
|
291
|
+
# Job-batching parameters (if None, they will be determined heuristically)
|
292
|
+
if wftask.meta is not None:
|
293
|
+
tasks_per_job = wftask.meta.get("tasks_per_job", None)
|
294
|
+
parallel_tasks_per_job = wftask.meta.get(
|
295
|
+
"parallel_tasks_per_job", None
|
296
|
+
)
|
297
|
+
else:
|
298
|
+
tasks_per_job = None
|
299
|
+
parallel_tasks_per_job = None
|
300
|
+
slurm_dict["tasks_per_job"] = tasks_per_job
|
301
|
+
slurm_dict["parallel_tasks_per_job"] = parallel_tasks_per_job
|
302
|
+
|
303
|
+
# Put everything together
|
304
|
+
logger.debug(
|
305
|
+
"[get_slurm_config] Now create a SlurmConfig object based "
|
306
|
+
f"on {slurm_dict=}"
|
307
|
+
)
|
308
|
+
slurm_config = SlurmConfig(**slurm_dict)
|
309
|
+
|
310
|
+
return slurm_config
|
@@ -15,12 +15,10 @@ implementation of `submit_setup_call` in
|
|
15
15
|
[fractal_server.app.runner._common][]).
|
16
16
|
"""
|
17
17
|
from pathlib import Path
|
18
|
-
from typing import Optional
|
19
18
|
|
20
|
-
from ...
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from ._slurm_config import get_slurm_config
|
19
|
+
from ...task_files import get_task_file_paths
|
20
|
+
from .get_slurm_config import get_slurm_config
|
21
|
+
from fractal_server.app.models.v1 import WorkflowTask
|
24
22
|
|
25
23
|
|
26
24
|
def _slurm_submit_setup(
|
@@ -28,7 +26,6 @@ def _slurm_submit_setup(
|
|
28
26
|
wftask: WorkflowTask,
|
29
27
|
workflow_dir: Path,
|
30
28
|
workflow_dir_user: Path,
|
31
|
-
task_pars: Optional[TaskParameters] = None,
|
32
29
|
) -> dict[str, object]:
|
33
30
|
"""
|
34
31
|
Collect WorfklowTask-specific configuration parameters from different
|
@@ -46,9 +43,6 @@ def _slurm_submit_setup(
|
|
46
43
|
Arguments:
|
47
44
|
wftask:
|
48
45
|
WorkflowTask for which the configuration is to be assembled
|
49
|
-
task_pars:
|
50
|
-
Task parameters to be passed to the task
|
51
|
-
(not used in this function)
|
52
46
|
workflow_dir:
|
53
47
|
Server-owned directory to store all task-execution-related relevant
|
54
48
|
files (inputs, outputs, errors, and all meta files related to the
|