fractal-server 2.13.0__py3-none-any.whl → 2.14.0__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.
Files changed (127) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/__main__.py +3 -1
  3. fractal_server/app/models/linkusergroup.py +6 -2
  4. fractal_server/app/models/v2/__init__.py +11 -1
  5. fractal_server/app/models/v2/accounting.py +35 -0
  6. fractal_server/app/models/v2/dataset.py +1 -11
  7. fractal_server/app/models/v2/history.py +78 -0
  8. fractal_server/app/models/v2/job.py +10 -3
  9. fractal_server/app/models/v2/task_group.py +2 -2
  10. fractal_server/app/models/v2/workflow.py +1 -1
  11. fractal_server/app/models/v2/workflowtask.py +1 -1
  12. fractal_server/app/routes/admin/v2/__init__.py +4 -0
  13. fractal_server/app/routes/admin/v2/accounting.py +98 -0
  14. fractal_server/app/routes/admin/v2/impersonate.py +35 -0
  15. fractal_server/app/routes/admin/v2/job.py +5 -13
  16. fractal_server/app/routes/admin/v2/task.py +1 -1
  17. fractal_server/app/routes/admin/v2/task_group.py +4 -29
  18. fractal_server/app/routes/api/__init__.py +1 -1
  19. fractal_server/app/routes/api/v2/__init__.py +8 -2
  20. fractal_server/app/routes/api/v2/_aux_functions.py +66 -0
  21. fractal_server/app/routes/api/v2/_aux_functions_history.py +166 -0
  22. fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
  23. fractal_server/app/routes/api/v2/dataset.py +0 -17
  24. fractal_server/app/routes/api/v2/history.py +544 -0
  25. fractal_server/app/routes/api/v2/images.py +31 -43
  26. fractal_server/app/routes/api/v2/job.py +30 -0
  27. fractal_server/app/routes/api/v2/project.py +1 -53
  28. fractal_server/app/routes/api/v2/{status.py → status_legacy.py} +6 -6
  29. fractal_server/app/routes/api/v2/submit.py +17 -14
  30. fractal_server/app/routes/api/v2/task.py +3 -10
  31. fractal_server/app/routes/api/v2/task_collection_custom.py +4 -9
  32. fractal_server/app/routes/api/v2/task_group.py +2 -22
  33. fractal_server/app/routes/api/v2/verify_image_types.py +61 -0
  34. fractal_server/app/routes/api/v2/workflow.py +28 -69
  35. fractal_server/app/routes/api/v2/workflowtask.py +53 -50
  36. fractal_server/app/routes/auth/group.py +0 -16
  37. fractal_server/app/routes/auth/oauth.py +5 -3
  38. fractal_server/app/routes/aux/__init__.py +0 -20
  39. fractal_server/app/routes/pagination.py +47 -0
  40. fractal_server/app/runner/components.py +0 -3
  41. fractal_server/app/runner/compress_folder.py +57 -29
  42. fractal_server/app/runner/exceptions.py +4 -0
  43. fractal_server/app/runner/executors/base_runner.py +157 -0
  44. fractal_server/app/runner/{v2/_local/_local_config.py → executors/local/get_local_config.py} +7 -9
  45. fractal_server/app/runner/executors/local/runner.py +248 -0
  46. fractal_server/app/runner/executors/{slurm → slurm_common}/_batching.py +1 -1
  47. fractal_server/app/runner/executors/{slurm → slurm_common}/_slurm_config.py +9 -7
  48. fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +868 -0
  49. fractal_server/app/runner/{v2/_slurm_common → executors/slurm_common}/get_slurm_config.py +48 -17
  50. fractal_server/app/runner/executors/{slurm → slurm_common}/remote.py +36 -47
  51. fractal_server/app/runner/executors/slurm_common/slurm_job_task_models.py +134 -0
  52. fractal_server/app/runner/executors/slurm_ssh/runner.py +268 -0
  53. fractal_server/app/runner/executors/slurm_sudo/__init__.py +0 -0
  54. fractal_server/app/runner/executors/{slurm/sudo → slurm_sudo}/_subprocess_run_as_user.py +2 -83
  55. fractal_server/app/runner/executors/slurm_sudo/runner.py +193 -0
  56. fractal_server/app/runner/extract_archive.py +1 -3
  57. fractal_server/app/runner/task_files.py +134 -87
  58. fractal_server/app/runner/v2/__init__.py +0 -395
  59. fractal_server/app/runner/v2/_local.py +88 -0
  60. fractal_server/app/runner/v2/{_slurm_ssh/__init__.py → _slurm_ssh.py} +22 -19
  61. fractal_server/app/runner/v2/{_slurm_sudo/__init__.py → _slurm_sudo.py} +19 -15
  62. fractal_server/app/runner/v2/db_tools.py +119 -0
  63. fractal_server/app/runner/v2/runner.py +219 -98
  64. fractal_server/app/runner/v2/runner_functions.py +491 -189
  65. fractal_server/app/runner/v2/runner_functions_low_level.py +40 -43
  66. fractal_server/app/runner/v2/submit_workflow.py +358 -0
  67. fractal_server/app/runner/v2/task_interface.py +31 -0
  68. fractal_server/app/schemas/_validators.py +13 -24
  69. fractal_server/app/schemas/user.py +10 -7
  70. fractal_server/app/schemas/user_settings.py +9 -21
  71. fractal_server/app/schemas/v2/__init__.py +10 -1
  72. fractal_server/app/schemas/v2/accounting.py +18 -0
  73. fractal_server/app/schemas/v2/dataset.py +12 -94
  74. fractal_server/app/schemas/v2/dumps.py +26 -9
  75. fractal_server/app/schemas/v2/history.py +80 -0
  76. fractal_server/app/schemas/v2/job.py +15 -8
  77. fractal_server/app/schemas/v2/manifest.py +14 -7
  78. fractal_server/app/schemas/v2/project.py +9 -7
  79. fractal_server/app/schemas/v2/status_legacy.py +35 -0
  80. fractal_server/app/schemas/v2/task.py +72 -77
  81. fractal_server/app/schemas/v2/task_collection.py +14 -32
  82. fractal_server/app/schemas/v2/task_group.py +10 -9
  83. fractal_server/app/schemas/v2/workflow.py +10 -11
  84. fractal_server/app/schemas/v2/workflowtask.py +2 -21
  85. fractal_server/app/security/__init__.py +3 -3
  86. fractal_server/app/security/signup_email.py +2 -2
  87. fractal_server/config.py +91 -90
  88. fractal_server/images/tools.py +23 -0
  89. fractal_server/migrations/versions/47351f8c7ebc_drop_dataset_filters.py +50 -0
  90. fractal_server/migrations/versions/9db60297b8b2_set_ondelete.py +250 -0
  91. fractal_server/migrations/versions/af1ef1c83c9b_add_accounting_tables.py +57 -0
  92. fractal_server/migrations/versions/c90a7c76e996_job_id_in_history_run.py +41 -0
  93. fractal_server/migrations/versions/e81103413827_add_job_type_filters.py +36 -0
  94. fractal_server/migrations/versions/f37aceb45062_make_historyunit_logfile_required.py +39 -0
  95. fractal_server/migrations/versions/fbce16ff4e47_new_history_items.py +120 -0
  96. fractal_server/ssh/_fabric.py +28 -14
  97. fractal_server/tasks/v2/local/collect.py +2 -2
  98. fractal_server/tasks/v2/ssh/collect.py +2 -2
  99. fractal_server/tasks/v2/templates/2_pip_install.sh +1 -1
  100. fractal_server/tasks/v2/templates/4_pip_show.sh +1 -1
  101. fractal_server/tasks/v2/utils_background.py +1 -20
  102. fractal_server/tasks/v2/utils_database.py +30 -17
  103. fractal_server/tasks/v2/utils_templates.py +6 -0
  104. {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/METADATA +4 -4
  105. {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/RECORD +114 -99
  106. {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/WHEEL +1 -1
  107. fractal_server/app/runner/executors/slurm/ssh/_executor_wait_thread.py +0 -126
  108. fractal_server/app/runner/executors/slurm/ssh/_slurm_job.py +0 -116
  109. fractal_server/app/runner/executors/slurm/ssh/executor.py +0 -1386
  110. fractal_server/app/runner/executors/slurm/sudo/_check_jobs_status.py +0 -71
  111. fractal_server/app/runner/executors/slurm/sudo/_executor_wait_thread.py +0 -130
  112. fractal_server/app/runner/executors/slurm/sudo/executor.py +0 -1281
  113. fractal_server/app/runner/v2/_local/__init__.py +0 -129
  114. fractal_server/app/runner/v2/_local/_submit_setup.py +0 -52
  115. fractal_server/app/runner/v2/_local/executor.py +0 -100
  116. fractal_server/app/runner/v2/_slurm_ssh/_submit_setup.py +0 -83
  117. fractal_server/app/runner/v2/_slurm_sudo/_submit_setup.py +0 -83
  118. fractal_server/app/runner/v2/handle_failed_job.py +0 -59
  119. fractal_server/app/schemas/v2/status.py +0 -16
  120. /fractal_server/app/{runner/executors/slurm → history}/__init__.py +0 -0
  121. /fractal_server/app/runner/executors/{slurm/ssh → local}/__init__.py +0 -0
  122. /fractal_server/app/runner/executors/{slurm/sudo → slurm_common}/__init__.py +0 -0
  123. /fractal_server/app/runner/executors/{_job_states.py → slurm_common/_job_states.py} +0 -0
  124. /fractal_server/app/runner/executors/{slurm → slurm_common}/utils_executors.py +0 -0
  125. /fractal_server/app/runner/{v2/_slurm_common → executors/slurm_ssh}/__init__.py +0 -0
  126. {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/LICENSE +0 -0
  127. {fractal_server-2.13.0.dist-info → fractal_server-2.14.0.dist-info}/entry_points.txt +0 -0
@@ -1,395 +0,0 @@
1
- """
2
- Runner backend subsystem root V2
3
-
4
- This module is the single entry point to the runner backend subsystem V2.
5
- Other subystems should only import this module and not its submodules or
6
- the individual backends.
7
- """
8
- import os
9
- import traceback
10
- from pathlib import Path
11
- from typing import Optional
12
-
13
- from sqlalchemy.orm import Session as DBSyncSession
14
-
15
- from ....config import get_settings
16
- from ....logger import get_logger
17
- from ....logger import reset_logger_handlers
18
- from ....logger import set_logger
19
- from ....ssh._fabric import FractalSSH
20
- from ....syringe import Inject
21
- from ....utils import get_timestamp
22
- from ....zip_tools import _zip_folder_to_file_and_remove
23
- from ...db import DB
24
- from ...models.v2 import DatasetV2
25
- from ...models.v2 import JobV2
26
- from ...models.v2 import WorkflowV2
27
- from ...schemas.v2 import JobStatusTypeV2
28
- from ..exceptions import JobExecutionError
29
- from ..exceptions import TaskExecutionError
30
- from ..executors.slurm.sudo._subprocess_run_as_user import _mkdir_as_user
31
- from ..filenames import WORKFLOW_LOG_FILENAME
32
- from ..task_files import task_subfolder_name
33
- from ._local import process_workflow as local_process_workflow
34
- from ._slurm_ssh import process_workflow as slurm_ssh_process_workflow
35
- from ._slurm_sudo import process_workflow as slurm_sudo_process_workflow
36
- from .handle_failed_job import mark_last_wftask_as_failed
37
- from fractal_server import __VERSION__
38
- from fractal_server.app.models import UserSettings
39
-
40
-
41
- _backends = {}
42
- _backends["local"] = local_process_workflow
43
- _backends["slurm"] = slurm_sudo_process_workflow
44
- _backends["slurm_ssh"] = slurm_ssh_process_workflow
45
-
46
-
47
- def fail_job(
48
- *,
49
- db: DBSyncSession,
50
- job: JobV2,
51
- log_msg: str,
52
- logger_name: str,
53
- emit_log: bool = False,
54
- ) -> None:
55
- logger = get_logger(logger_name=logger_name)
56
- if emit_log:
57
- logger.error(log_msg)
58
- reset_logger_handlers(logger)
59
- job.status = JobStatusTypeV2.FAILED
60
- job.end_timestamp = get_timestamp()
61
- job.log = log_msg
62
- db.merge(job)
63
- db.commit()
64
- db.close()
65
- return
66
-
67
-
68
- def submit_workflow(
69
- *,
70
- workflow_id: int,
71
- dataset_id: int,
72
- job_id: int,
73
- user_settings: UserSettings,
74
- worker_init: Optional[str] = None,
75
- slurm_user: Optional[str] = None,
76
- user_cache_dir: Optional[str] = None,
77
- fractal_ssh: Optional[FractalSSH] = None,
78
- ) -> None:
79
- """
80
- Prepares a workflow and applies it to a dataset
81
-
82
- This function wraps the process_workflow one, which is different for each
83
- backend (e.g. local or slurm backend).
84
-
85
- Args:
86
- workflow_id:
87
- ID of the workflow being applied
88
- dataset_id:
89
- Dataset ID
90
- job_id:
91
- Id of the job record which stores the state for the current
92
- workflow application.
93
- worker_init:
94
- Custom executor parameters that get parsed before the execution of
95
- each task.
96
- user_cache_dir:
97
- Cache directory (namely a path where the user can write); for the
98
- slurm backend, this is used as a base directory for
99
- `job.working_dir_user`.
100
- slurm_user:
101
- The username to impersonate for the workflow execution, for the
102
- slurm backend.
103
- """
104
- # Declare runner backend and set `process_workflow` function
105
- settings = Inject(get_settings)
106
- FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
107
- logger_name = f"WF{workflow_id}_job{job_id}"
108
- logger = set_logger(logger_name=logger_name)
109
-
110
- with next(DB.get_sync_db()) as db_sync:
111
- try:
112
- job: Optional[JobV2] = db_sync.get(JobV2, job_id)
113
- dataset: Optional[DatasetV2] = db_sync.get(DatasetV2, dataset_id)
114
- workflow: Optional[WorkflowV2] = db_sync.get(
115
- WorkflowV2, workflow_id
116
- )
117
- except Exception as e:
118
- logger.error(
119
- f"Error conneting to the database. Original error: {str(e)}"
120
- )
121
- reset_logger_handlers(logger)
122
- return
123
-
124
- if job is None:
125
- logger.error(f"JobV2 {job_id} does not exist")
126
- reset_logger_handlers(logger)
127
- return
128
- if dataset is None or workflow is None:
129
- log_msg = ""
130
- if not dataset:
131
- log_msg += f"Cannot fetch dataset {dataset_id} from database\n"
132
- if not workflow:
133
- log_msg += (
134
- f"Cannot fetch workflow {workflow_id} from database\n"
135
- )
136
- fail_job(
137
- db=db_sync, job=job, log_msg=log_msg, logger_name=logger_name
138
- )
139
- return
140
-
141
- # Declare runner backend and set `process_workflow` function
142
- settings = Inject(get_settings)
143
- FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
144
- try:
145
- process_workflow = _backends[settings.FRACTAL_RUNNER_BACKEND]
146
- except KeyError as e:
147
- fail_job(
148
- db=db_sync,
149
- job=job,
150
- log_msg=(
151
- f"Invalid {FRACTAL_RUNNER_BACKEND=}.\n"
152
- f"Original KeyError: {str(e)}"
153
- ),
154
- logger_name=logger_name,
155
- emit_log=True,
156
- )
157
- return
158
-
159
- # Define and create server-side working folder
160
- WORKFLOW_DIR_LOCAL = Path(job.working_dir)
161
- if WORKFLOW_DIR_LOCAL.exists():
162
- fail_job(
163
- db=db_sync,
164
- job=job,
165
- log_msg=f"Workflow dir {WORKFLOW_DIR_LOCAL} already exists.",
166
- logger_name=logger_name,
167
- emit_log=True,
168
- )
169
- return
170
-
171
- try:
172
- # Create WORKFLOW_DIR_LOCAL
173
- if FRACTAL_RUNNER_BACKEND == "slurm":
174
- original_umask = os.umask(0)
175
- WORKFLOW_DIR_LOCAL.mkdir(parents=True, mode=0o755)
176
- os.umask(original_umask)
177
- else:
178
- WORKFLOW_DIR_LOCAL.mkdir(parents=True)
179
-
180
- # Define and create WORKFLOW_DIR_REMOTE
181
- if FRACTAL_RUNNER_BACKEND == "local":
182
- WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
183
- elif FRACTAL_RUNNER_BACKEND == "slurm":
184
- WORKFLOW_DIR_REMOTE = (
185
- Path(user_cache_dir) / WORKFLOW_DIR_LOCAL.name
186
- )
187
- _mkdir_as_user(
188
- folder=str(WORKFLOW_DIR_REMOTE), user=slurm_user
189
- )
190
- elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
191
- # Folder creation is deferred to _process_workflow
192
- WORKFLOW_DIR_REMOTE = (
193
- Path(user_settings.ssh_jobs_dir) / WORKFLOW_DIR_LOCAL.name
194
- )
195
- else:
196
- logger.error(
197
- "Invalid FRACTAL_RUNNER_BACKEND="
198
- f"{settings.FRACTAL_RUNNER_BACKEND}."
199
- )
200
-
201
- # Create all tasks subfolders
202
- for order in range(job.first_task_index, job.last_task_index + 1):
203
- this_wftask = workflow.task_list[order]
204
- task_name = this_wftask.task.name
205
- subfolder_name = task_subfolder_name(
206
- order=order,
207
- task_name=task_name,
208
- )
209
- if FRACTAL_RUNNER_BACKEND == "slurm":
210
- # Create local subfolder (with 755) and remote one
211
- # (via `sudo -u`)
212
- original_umask = os.umask(0)
213
- (WORKFLOW_DIR_LOCAL / subfolder_name).mkdir(mode=0o755)
214
- os.umask(original_umask)
215
- _mkdir_as_user(
216
- folder=str(WORKFLOW_DIR_REMOTE / subfolder_name),
217
- user=slurm_user,
218
- )
219
- else:
220
- # Create local subfolder (with standard permission set)
221
- (WORKFLOW_DIR_LOCAL / subfolder_name).mkdir()
222
- logger.info("Skip remote-subfolder creation")
223
- except Exception as e:
224
- error_type = type(e).__name__
225
- fail_job(
226
- db=db_sync,
227
- job=job,
228
- log_msg=(
229
- f"{error_type} error occurred while creating job folder "
230
- f"and subfolders.\nOriginal error: {str(e)}"
231
- ),
232
- logger_name=logger_name,
233
- emit_log=True,
234
- )
235
- return
236
-
237
- # After Session.commit() is called, either explicitly or when using a
238
- # context manager, all objects associated with the Session are expired.
239
- # https://docs.sqlalchemy.org/en/14/orm/
240
- # session_basics.html#opening-and-closing-a-session
241
- # https://docs.sqlalchemy.org/en/14/orm/
242
- # session_state_management.html#refreshing-expiring
243
-
244
- # See issue #928:
245
- # https://github.com/fractal-analytics-platform/
246
- # fractal-server/issues/928
247
-
248
- db_sync.refresh(dataset)
249
- db_sync.refresh(workflow)
250
- for wftask in workflow.task_list:
251
- db_sync.refresh(wftask)
252
-
253
- # Write logs
254
- log_file_path = WORKFLOW_DIR_LOCAL / WORKFLOW_LOG_FILENAME
255
- logger = set_logger(
256
- logger_name=logger_name,
257
- log_file_path=log_file_path,
258
- )
259
- logger.info(
260
- f'Start execution of workflow "{workflow.name}"; '
261
- f"more logs at {str(log_file_path)}"
262
- )
263
- logger.debug(f"fractal_server.__VERSION__: {__VERSION__}")
264
- logger.debug(f"FRACTAL_RUNNER_BACKEND: {FRACTAL_RUNNER_BACKEND}")
265
- if FRACTAL_RUNNER_BACKEND == "slurm":
266
- logger.debug(f"slurm_user: {slurm_user}")
267
- logger.debug(f"slurm_account: {job.slurm_account}")
268
- logger.debug(f"worker_init: {worker_init}")
269
- elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
270
- logger.debug(f"ssh_user: {user_settings.ssh_username}")
271
- logger.debug(f"base dir: {user_settings.ssh_tasks_dir}")
272
- logger.debug(f"worker_init: {worker_init}")
273
- logger.debug(f"job.id: {job.id}")
274
- logger.debug(f"job.working_dir: {job.working_dir}")
275
- logger.debug(f"job.working_dir_user: {job.working_dir_user}")
276
- logger.debug(f"job.first_task_index: {job.first_task_index}")
277
- logger.debug(f"job.last_task_index: {job.last_task_index}")
278
- logger.debug(f'START workflow "{workflow.name}"')
279
-
280
- try:
281
- if FRACTAL_RUNNER_BACKEND == "local":
282
- process_workflow = local_process_workflow
283
- backend_specific_kwargs = {}
284
- elif FRACTAL_RUNNER_BACKEND == "slurm":
285
- process_workflow = slurm_sudo_process_workflow
286
- backend_specific_kwargs = dict(
287
- slurm_user=slurm_user,
288
- slurm_account=job.slurm_account,
289
- user_cache_dir=user_cache_dir,
290
- )
291
- elif FRACTAL_RUNNER_BACKEND == "slurm_ssh":
292
- process_workflow = slurm_ssh_process_workflow
293
- backend_specific_kwargs = dict(fractal_ssh=fractal_ssh)
294
- else:
295
- raise RuntimeError(
296
- f"Invalid runner backend {FRACTAL_RUNNER_BACKEND=}"
297
- )
298
-
299
- # "The Session.close() method does not prevent the Session from being
300
- # used again. The Session itself does not actually have a distinct
301
- # “closed” state; it merely means the Session will release all database
302
- # connections and ORM objects."
303
- # (https://docs.sqlalchemy.org/en/20/orm/session_api.html#sqlalchemy.orm.Session.close).
304
- #
305
- # We close the session before the (possibly long) process_workflow
306
- # call, to make sure all DB connections are released. The reason why we
307
- # are not using a context manager within the try block is that we also
308
- # need access to db_sync in the except branches.
309
- db_sync = next(DB.get_sync_db())
310
- db_sync.close()
311
-
312
- process_workflow(
313
- workflow=workflow,
314
- dataset=dataset,
315
- workflow_dir_local=WORKFLOW_DIR_LOCAL,
316
- workflow_dir_remote=WORKFLOW_DIR_REMOTE,
317
- logger_name=logger_name,
318
- worker_init=worker_init,
319
- first_task_index=job.first_task_index,
320
- last_task_index=job.last_task_index,
321
- job_attribute_filters=job.attribute_filters,
322
- **backend_specific_kwargs,
323
- )
324
-
325
- logger.info(
326
- f'End execution of workflow "{workflow.name}"; '
327
- f"more logs at {str(log_file_path)}"
328
- )
329
- logger.debug(f'END workflow "{workflow.name}"')
330
-
331
- # Update job DB entry
332
- job.status = JobStatusTypeV2.DONE
333
- job.end_timestamp = get_timestamp()
334
- with log_file_path.open("r") as f:
335
- logs = f.read()
336
- job.log = logs
337
- db_sync.merge(job)
338
- db_sync.commit()
339
-
340
- except TaskExecutionError as e:
341
- logger.debug(f'FAILED workflow "{workflow.name}", TaskExecutionError.')
342
- logger.info(f'Workflow "{workflow.name}" failed (TaskExecutionError).')
343
-
344
- mark_last_wftask_as_failed(
345
- dataset_id=dataset_id,
346
- logger_name=logger_name,
347
- )
348
- exception_args_string = "\n".join(e.args)
349
- log_msg = (
350
- f"TASK ERROR: "
351
- f"Task name: {e.task_name}, "
352
- f"position in Workflow: {e.workflow_task_order}\n"
353
- f"TRACEBACK:\n{exception_args_string}"
354
- )
355
- fail_job(db=db_sync, job=job, log_msg=log_msg, logger_name=logger_name)
356
-
357
- except JobExecutionError as e:
358
- logger.debug(f'FAILED workflow "{workflow.name}", JobExecutionError.')
359
- logger.info(f'Workflow "{workflow.name}" failed (JobExecutionError).')
360
- mark_last_wftask_as_failed(
361
- dataset_id=dataset_id,
362
- logger_name=logger_name,
363
- )
364
- fail_job(
365
- db=db_sync,
366
- job=job,
367
- log_msg=(
368
- f"JOB ERROR in Fractal job {job.id}:\n"
369
- f"TRACEBACK:\n{e.assemble_error()}"
370
- ),
371
- logger_name=logger_name,
372
- )
373
-
374
- except Exception:
375
- logger.debug(f'FAILED workflow "{workflow.name}", unknown error.')
376
- logger.info(f'Workflow "{workflow.name}" failed (unkwnon error).')
377
- mark_last_wftask_as_failed(
378
- dataset_id=dataset_id,
379
- logger_name=logger_name,
380
- )
381
- current_traceback = traceback.format_exc()
382
- fail_job(
383
- db=db_sync,
384
- job=job,
385
- log_msg=(
386
- f"UNKNOWN ERROR in Fractal job {job.id}\n"
387
- f"TRACEBACK:\n{current_traceback}"
388
- ),
389
- logger_name=logger_name,
390
- )
391
-
392
- finally:
393
- reset_logger_handlers(logger)
394
- db_sync.close()
395
- _zip_folder_to_file_and_remove(folder=job.working_dir)
@@ -0,0 +1,88 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ from ...models.v2 import DatasetV2
5
+ from ...models.v2 import WorkflowV2
6
+ from ..executors.local.get_local_config import get_local_backend_config
7
+ from ..executors.local.runner import LocalRunner
8
+ from ..set_start_and_last_task_index import set_start_and_last_task_index
9
+ from .runner import execute_tasks_v2
10
+ from fractal_server.images.models import AttributeFiltersType
11
+
12
+
13
+ def process_workflow(
14
+ *,
15
+ workflow: WorkflowV2,
16
+ dataset: DatasetV2,
17
+ workflow_dir_local: Path,
18
+ job_id: int,
19
+ workflow_dir_remote: Optional[Path] = None,
20
+ first_task_index: Optional[int] = None,
21
+ last_task_index: Optional[int] = None,
22
+ logger_name: str,
23
+ job_attribute_filters: AttributeFiltersType,
24
+ job_type_filters: dict[str, bool],
25
+ user_id: int,
26
+ **kwargs,
27
+ ) -> None:
28
+ """
29
+ Run a workflow through
30
+
31
+ Args:
32
+ workflow:
33
+ The workflow to be run
34
+ dataset:
35
+ Initial dataset.
36
+ workflow_dir_local:
37
+ Working directory for this run.
38
+ workflow_dir_remote:
39
+ Working directory for this run, on the user side. This argument is
40
+ present for compatibility with the standard backend interface, but
41
+ for the `local` backend it cannot be different from
42
+ `workflow_dir_local`.
43
+ first_task_index:
44
+ Positional index of the first task to execute; if `None`, start
45
+ from `0`.
46
+ last_task_index:
47
+ Positional index of the last task to execute; if `None`, proceed
48
+ until the last task.
49
+ logger_name: Logger name
50
+ user_id:
51
+
52
+ Raises:
53
+ TaskExecutionError: wrapper for errors raised during tasks' execution
54
+ (positive exit codes).
55
+ JobExecutionError: wrapper for errors raised by the tasks' executors
56
+ (negative exit codes).
57
+ """
58
+
59
+ if workflow_dir_remote and (workflow_dir_remote != workflow_dir_local):
60
+ raise NotImplementedError(
61
+ "Local backend does not support different directories "
62
+ f"{workflow_dir_local=} and {workflow_dir_remote=}"
63
+ )
64
+
65
+ # Set values of first_task_index and last_task_index
66
+ num_tasks = len(workflow.task_list)
67
+ first_task_index, last_task_index = set_start_and_last_task_index(
68
+ num_tasks,
69
+ first_task_index=first_task_index,
70
+ last_task_index=last_task_index,
71
+ )
72
+
73
+ with LocalRunner(root_dir_local=workflow_dir_local) as runner:
74
+ execute_tasks_v2(
75
+ wf_task_list=workflow.task_list[
76
+ first_task_index : (last_task_index + 1)
77
+ ],
78
+ dataset=dataset,
79
+ job_id=job_id,
80
+ runner=runner,
81
+ workflow_dir_local=workflow_dir_local,
82
+ workflow_dir_remote=workflow_dir_local,
83
+ logger_name=logger_name,
84
+ get_runner_config=get_local_backend_config,
85
+ job_attribute_filters=job_attribute_filters,
86
+ job_type_filters=job_type_filters,
87
+ user_id=user_id,
88
+ )
@@ -11,7 +11,7 @@
11
11
  # Institute for Biomedical Research and Pelkmans Lab from the University of
12
12
  # Zurich.
13
13
  """
14
- Slurm Bakend
14
+ Slurm Backend
15
15
 
16
16
  This backend runs fractal workflows in a SLURM cluster using Clusterfutures
17
17
  Executor objects.
@@ -19,14 +19,14 @@ Executor objects.
19
19
  from pathlib import Path
20
20
  from typing import Optional
21
21
 
22
- from .....ssh._fabric import FractalSSH
23
- from ....models.v2 import DatasetV2
24
- from ....models.v2 import WorkflowV2
25
- from ...exceptions import JobExecutionError
26
- from ...executors.slurm.ssh.executor import FractalSlurmSSHExecutor
27
- from ...set_start_and_last_task_index import set_start_and_last_task_index
28
- from ..runner import execute_tasks_v2
29
- from ._submit_setup import _slurm_submit_setup
22
+ from ....ssh._fabric import FractalSSH
23
+ from ...models.v2 import DatasetV2
24
+ from ...models.v2 import WorkflowV2
25
+ from ..exceptions import JobExecutionError
26
+ from ..executors.slurm_common.get_slurm_config import get_slurm_config
27
+ from ..executors.slurm_ssh.runner import SlurmSSHRunner
28
+ from ..set_start_and_last_task_index import set_start_and_last_task_index
29
+ from .runner import execute_tasks_v2
30
30
  from fractal_server.images.models import AttributeFiltersType
31
31
  from fractal_server.logger import set_logger
32
32
 
@@ -38,17 +38,17 @@ def process_workflow(
38
38
  workflow: WorkflowV2,
39
39
  dataset: DatasetV2,
40
40
  workflow_dir_local: Path,
41
+ job_id: int,
41
42
  workflow_dir_remote: Optional[Path] = None,
42
43
  first_task_index: Optional[int] = None,
43
44
  last_task_index: Optional[int] = None,
44
45
  logger_name: str,
45
46
  job_attribute_filters: AttributeFiltersType,
47
+ job_type_filters: dict[str, bool],
46
48
  fractal_ssh: FractalSSH,
47
49
  worker_init: Optional[str] = None,
48
- # Not used
49
- user_cache_dir: Optional[str] = None,
50
- slurm_user: Optional[str] = None,
51
- slurm_account: Optional[str] = None,
50
+ user_id: int,
51
+ **kwargs, # not used
52
52
  ) -> None:
53
53
  """
54
54
  Process workflow (SLURM backend public interface)
@@ -77,21 +77,24 @@ def process_workflow(
77
77
  logger.error(error_msg)
78
78
  raise JobExecutionError(info=error_msg)
79
79
 
80
- with FractalSlurmSSHExecutor(
80
+ with SlurmSSHRunner(
81
81
  fractal_ssh=fractal_ssh,
82
- workflow_dir_local=workflow_dir_local,
83
- workflow_dir_remote=workflow_dir_remote,
82
+ root_dir_local=workflow_dir_local,
83
+ root_dir_remote=workflow_dir_remote,
84
84
  common_script_lines=worker_init,
85
- ) as executor:
85
+ ) as runner:
86
86
  execute_tasks_v2(
87
87
  wf_task_list=workflow.task_list[
88
88
  first_task_index : (last_task_index + 1)
89
89
  ],
90
90
  dataset=dataset,
91
- executor=executor,
91
+ job_id=job_id,
92
+ runner=runner,
92
93
  workflow_dir_local=workflow_dir_local,
93
94
  workflow_dir_remote=workflow_dir_remote,
94
95
  logger_name=logger_name,
95
- submit_setup_call=_slurm_submit_setup,
96
+ get_runner_config=get_slurm_config,
96
97
  job_attribute_filters=job_attribute_filters,
98
+ job_type_filters=job_type_filters,
99
+ user_id=user_id,
97
100
  )
@@ -11,7 +11,7 @@
11
11
  # Institute for Biomedical Research and Pelkmans Lab from the University of
12
12
  # Zurich.
13
13
  """
14
- Slurm Bakend
14
+ Slurm Backend
15
15
 
16
16
  This backend runs fractal workflows in a SLURM cluster using Clusterfutures
17
17
  Executor objects.
@@ -19,12 +19,12 @@ Executor objects.
19
19
  from pathlib import Path
20
20
  from typing import Optional
21
21
 
22
- from ....models.v2 import DatasetV2
23
- from ....models.v2 import WorkflowV2
24
- from ...executors.slurm.sudo.executor import FractalSlurmSudoExecutor
25
- from ...set_start_and_last_task_index import set_start_and_last_task_index
26
- from ..runner import execute_tasks_v2
27
- from ._submit_setup import _slurm_submit_setup
22
+ from ...models.v2 import DatasetV2
23
+ from ...models.v2 import WorkflowV2
24
+ from ..executors.slurm_common.get_slurm_config import get_slurm_config
25
+ from ..executors.slurm_sudo.runner import SudoSlurmRunner
26
+ from ..set_start_and_last_task_index import set_start_and_last_task_index
27
+ from .runner import execute_tasks_v2
28
28
  from fractal_server.images.models import AttributeFiltersType
29
29
 
30
30
 
@@ -33,11 +33,14 @@ def process_workflow(
33
33
  workflow: WorkflowV2,
34
34
  dataset: DatasetV2,
35
35
  workflow_dir_local: Path,
36
+ job_id: int,
36
37
  workflow_dir_remote: Optional[Path] = None,
37
38
  first_task_index: Optional[int] = None,
38
39
  last_task_index: Optional[int] = None,
39
40
  logger_name: str,
40
41
  job_attribute_filters: AttributeFiltersType,
42
+ job_type_filters: dict[str, bool],
43
+ user_id: int,
41
44
  # Slurm-specific
42
45
  user_cache_dir: Optional[str] = None,
43
46
  slurm_user: Optional[str] = None,
@@ -64,25 +67,26 @@ def process_workflow(
64
67
  if isinstance(worker_init, str):
65
68
  worker_init = worker_init.split("\n")
66
69
 
67
- with FractalSlurmSudoExecutor(
68
- debug=True,
69
- keep_logs=True,
70
+ with SudoSlurmRunner(
70
71
  slurm_user=slurm_user,
71
72
  user_cache_dir=user_cache_dir,
72
- workflow_dir_local=workflow_dir_local,
73
- workflow_dir_remote=workflow_dir_remote,
73
+ root_dir_local=workflow_dir_local,
74
+ root_dir_remote=workflow_dir_remote,
74
75
  common_script_lines=worker_init,
75
76
  slurm_account=slurm_account,
76
- ) as executor:
77
+ ) as runner:
77
78
  execute_tasks_v2(
78
79
  wf_task_list=workflow.task_list[
79
80
  first_task_index : (last_task_index + 1)
80
81
  ],
81
82
  dataset=dataset,
82
- executor=executor,
83
+ job_id=job_id,
84
+ runner=runner,
83
85
  workflow_dir_local=workflow_dir_local,
84
86
  workflow_dir_remote=workflow_dir_remote,
85
87
  logger_name=logger_name,
86
- submit_setup_call=_slurm_submit_setup,
88
+ get_runner_config=get_slurm_config,
87
89
  job_attribute_filters=job_attribute_filters,
90
+ job_type_filters=job_type_filters,
91
+ user_id=user_id,
88
92
  )