fractal-server 2.0.6__py3-none-any.whl → 2.2.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.
- fractal_server/__init__.py +1 -1
- fractal_server/app/db/__init__.py +1 -1
- fractal_server/app/routes/admin/v1.py +2 -4
- fractal_server/app/routes/admin/v2.py +2 -4
- fractal_server/app/routes/api/v1/_aux_functions.py +24 -0
- fractal_server/app/routes/api/v1/job.py +3 -4
- fractal_server/app/routes/api/v1/project.py +28 -18
- fractal_server/app/routes/api/v2/_aux_functions.py +35 -12
- fractal_server/app/routes/api/v2/job.py +3 -4
- fractal_server/app/routes/api/v2/project.py +21 -0
- fractal_server/app/routes/api/v2/submit.py +36 -15
- fractal_server/app/routes/aux/_job.py +3 -1
- fractal_server/app/routes/aux/_runner.py +3 -3
- fractal_server/app/runner/executors/slurm/executor.py +169 -68
- fractal_server/app/runner/shutdown.py +88 -0
- fractal_server/app/runner/task_files.py +59 -27
- fractal_server/app/runner/v1/__init__.py +113 -64
- fractal_server/app/runner/v1/_common.py +53 -51
- fractal_server/app/runner/v1/_local/__init__.py +12 -11
- fractal_server/app/runner/v1/_local/_submit_setup.py +4 -4
- fractal_server/app/runner/v1/_slurm/__init__.py +16 -16
- fractal_server/app/runner/v1/_slurm/_submit_setup.py +11 -10
- fractal_server/app/runner/v1/_slurm/get_slurm_config.py +6 -6
- fractal_server/app/runner/v2/__init__.py +139 -60
- fractal_server/app/runner/v2/_local/__init__.py +12 -11
- fractal_server/app/runner/v2/_local/_local_config.py +1 -1
- fractal_server/app/runner/v2/_local/_submit_setup.py +4 -4
- fractal_server/app/runner/v2/_local_experimental/__init__.py +155 -0
- fractal_server/app/runner/v2/_local_experimental/_local_config.py +108 -0
- fractal_server/app/runner/v2/_local_experimental/_submit_setup.py +42 -0
- fractal_server/app/runner/v2/_local_experimental/executor.py +156 -0
- fractal_server/app/runner/v2/_slurm/__init__.py +10 -10
- fractal_server/app/runner/v2/_slurm/_submit_setup.py +11 -10
- fractal_server/app/runner/v2/_slurm/get_slurm_config.py +6 -6
- fractal_server/app/runner/v2/runner.py +17 -15
- fractal_server/app/runner/v2/runner_functions.py +38 -38
- fractal_server/app/runner/v2/runner_functions_low_level.py +12 -6
- fractal_server/app/security/__init__.py +4 -5
- fractal_server/config.py +73 -19
- fractal_server/gunicorn_fractal.py +40 -0
- fractal_server/{logger/__init__.py → logger.py} +2 -2
- fractal_server/main.py +45 -26
- fractal_server/migrations/env.py +1 -1
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/METADATA +4 -1
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/RECORD +48 -43
- fractal_server/logger/gunicorn_logger.py +0 -19
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/LICENSE +0 -0
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/WHEEL +0 -0
- {fractal_server-2.0.6.dist-info → fractal_server-2.2.0.dist-info}/entry_points.txt +0 -0
fractal_server/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "2.0
|
1
|
+
__VERSION__ = "2.2.0"
|
@@ -35,7 +35,7 @@ from ...schemas.v1 import WorkflowReadV1
|
|
35
35
|
from ...security import current_active_superuser
|
36
36
|
from ..aux._job import _write_shutdown_file
|
37
37
|
from ..aux._job import _zip_folder_to_byte_stream
|
38
|
-
from ..aux._runner import
|
38
|
+
from ..aux._runner import _is_shutdown_available
|
39
39
|
|
40
40
|
router_admin_v1 = APIRouter()
|
41
41
|
|
@@ -349,11 +349,9 @@ async def stop_job(
|
|
349
349
|
) -> Response:
|
350
350
|
"""
|
351
351
|
Stop execution of a workflow job.
|
352
|
-
|
353
|
-
Only available for slurm backend.
|
354
352
|
"""
|
355
353
|
|
356
|
-
|
354
|
+
_is_shutdown_available()
|
357
355
|
|
358
356
|
job = await db.get(ApplyWorkflow, job_id)
|
359
357
|
if job is None:
|
@@ -38,7 +38,7 @@ from ...schemas.v2 import ProjectReadV2
|
|
38
38
|
from ...security import current_active_superuser
|
39
39
|
from ..aux._job import _write_shutdown_file
|
40
40
|
from ..aux._job import _zip_folder_to_byte_stream
|
41
|
-
from ..aux._runner import
|
41
|
+
from ..aux._runner import _is_shutdown_available
|
42
42
|
|
43
43
|
router_admin_v2 = APIRouter()
|
44
44
|
|
@@ -236,11 +236,9 @@ async def stop_job(
|
|
236
236
|
) -> Response:
|
237
237
|
"""
|
238
238
|
Stop execution of a workflow job.
|
239
|
-
|
240
|
-
Only available for slurm backend.
|
241
239
|
"""
|
242
240
|
|
243
|
-
|
241
|
+
_is_shutdown_available()
|
244
242
|
|
245
243
|
job = await db.get(JobV2, job_id)
|
246
244
|
if job is None:
|
@@ -436,3 +436,27 @@ async def _workflow_insert_task(
|
|
436
436
|
await db.refresh(wf_task)
|
437
437
|
|
438
438
|
return wf_task
|
439
|
+
|
440
|
+
|
441
|
+
async def clean_app_job_list_v1(
|
442
|
+
db: AsyncSession, jobs_list: list[int]
|
443
|
+
) -> list[int]:
|
444
|
+
"""
|
445
|
+
Remove from a job list all jobs with status different from submitted.
|
446
|
+
|
447
|
+
Args:
|
448
|
+
db: Async database session
|
449
|
+
jobs_list: List of job IDs currently associated to the app.
|
450
|
+
|
451
|
+
Return:
|
452
|
+
List of IDs for submitted jobs.
|
453
|
+
"""
|
454
|
+
stmt = select(ApplyWorkflow).where(ApplyWorkflow.id.in_(jobs_list))
|
455
|
+
result = await db.execute(stmt)
|
456
|
+
db_jobs_list = result.scalars().all()
|
457
|
+
submitted_job_ids = [
|
458
|
+
job.id
|
459
|
+
for job in db_jobs_list
|
460
|
+
if job.status == JobStatusTypeV1.SUBMITTED
|
461
|
+
]
|
462
|
+
return submitted_job_ids
|
@@ -19,7 +19,7 @@ from ....security import current_active_user
|
|
19
19
|
from ....security import User
|
20
20
|
from ...aux._job import _write_shutdown_file
|
21
21
|
from ...aux._job import _zip_folder_to_byte_stream
|
22
|
-
from ...aux._runner import
|
22
|
+
from ...aux._runner import _is_shutdown_available
|
23
23
|
from ._aux_functions import _get_job_check_owner
|
24
24
|
from ._aux_functions import _get_project_check_owner
|
25
25
|
from ._aux_functions import _get_workflow_check_owner
|
@@ -177,11 +177,10 @@ async def stop_job(
|
|
177
177
|
db: AsyncSession = Depends(get_async_db),
|
178
178
|
) -> Response:
|
179
179
|
"""
|
180
|
-
Stop execution of a workflow job
|
180
|
+
Stop execution of a workflow job.
|
181
181
|
"""
|
182
182
|
|
183
|
-
|
184
|
-
_check_backend_is_slurm()
|
183
|
+
_is_shutdown_available()
|
185
184
|
|
186
185
|
# Get job from DB
|
187
186
|
output = await _get_job_check_owner(
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import os
|
1
2
|
from datetime import datetime
|
2
3
|
from datetime import timedelta
|
3
4
|
from datetime import timezone
|
@@ -7,13 +8,12 @@ from fastapi import APIRouter
|
|
7
8
|
from fastapi import BackgroundTasks
|
8
9
|
from fastapi import Depends
|
9
10
|
from fastapi import HTTPException
|
11
|
+
from fastapi import Request
|
10
12
|
from fastapi import Response
|
11
13
|
from fastapi import status
|
12
|
-
from sqlalchemy.exc import IntegrityError
|
13
14
|
from sqlmodel import select
|
14
15
|
|
15
16
|
from .....config import get_settings
|
16
|
-
from .....logger import close_logger
|
17
17
|
from .....logger import set_logger
|
18
18
|
from .....syringe import Inject
|
19
19
|
from ....db import AsyncSession
|
@@ -42,8 +42,10 @@ from ._aux_functions import _get_dataset_check_owner
|
|
42
42
|
from ._aux_functions import _get_project_check_owner
|
43
43
|
from ._aux_functions import _get_submitted_jobs_statement
|
44
44
|
from ._aux_functions import _get_workflow_check_owner
|
45
|
+
from ._aux_functions import clean_app_job_list_v1
|
45
46
|
|
46
47
|
router = APIRouter()
|
48
|
+
logger = set_logger(__name__)
|
47
49
|
|
48
50
|
|
49
51
|
def _encode_as_utc(dt: datetime):
|
@@ -86,20 +88,11 @@ async def create_project(
|
|
86
88
|
|
87
89
|
db_project = Project(**project.dict())
|
88
90
|
db_project.user_list.append(user)
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
except IntegrityError as e:
|
95
|
-
await db.rollback()
|
96
|
-
logger = set_logger("create_project")
|
97
|
-
logger.error(str(e))
|
98
|
-
close_logger(logger)
|
99
|
-
raise HTTPException(
|
100
|
-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
101
|
-
detail=str(e),
|
102
|
-
)
|
91
|
+
|
92
|
+
db.add(db_project)
|
93
|
+
await db.commit()
|
94
|
+
await db.refresh(db_project)
|
95
|
+
await db.close()
|
103
96
|
|
104
97
|
return db_project
|
105
98
|
|
@@ -252,10 +245,23 @@ async def apply_workflow(
|
|
252
245
|
background_tasks: BackgroundTasks,
|
253
246
|
input_dataset_id: int,
|
254
247
|
output_dataset_id: int,
|
248
|
+
request: Request,
|
255
249
|
user: User = Depends(current_active_verified_user),
|
256
250
|
db: AsyncSession = Depends(get_async_db),
|
257
251
|
) -> Optional[ApplyWorkflowReadV1]:
|
258
252
|
|
253
|
+
# Remove non-submitted V1 jobs from the app state when the list grows
|
254
|
+
# beyond a threshold
|
255
|
+
settings = Inject(get_settings)
|
256
|
+
if (
|
257
|
+
len(request.app.state.jobsV1)
|
258
|
+
> settings.FRACTAL_API_MAX_JOB_LIST_LENGTH
|
259
|
+
):
|
260
|
+
new_jobs_list = await clean_app_job_list_v1(
|
261
|
+
db, request.app.state.jobsV1
|
262
|
+
)
|
263
|
+
request.app.state.jobsV1 = new_jobs_list
|
264
|
+
|
259
265
|
output = await _get_dataset_check_owner(
|
260
266
|
project_id=project_id,
|
261
267
|
dataset_id=input_dataset_id,
|
@@ -312,7 +318,6 @@ async def apply_workflow(
|
|
312
318
|
)
|
313
319
|
|
314
320
|
# If backend is SLURM, check that the user has required attributes
|
315
|
-
settings = Inject(get_settings)
|
316
321
|
backend = settings.FRACTAL_RUNNER_BACKEND
|
317
322
|
if backend == "slurm":
|
318
323
|
if not user.slurm_user:
|
@@ -474,7 +479,12 @@ async def apply_workflow(
|
|
474
479
|
slurm_user=user.slurm_user,
|
475
480
|
user_cache_dir=user.cache_dir,
|
476
481
|
)
|
477
|
-
|
482
|
+
request.app.state.jobsV1.append(job.id)
|
483
|
+
logger.info(
|
484
|
+
f"Current worker's pid is {os.getpid()}. "
|
485
|
+
f"Current status of worker job's list "
|
486
|
+
f"{request.app.state.jobsV1}"
|
487
|
+
)
|
478
488
|
await db.close()
|
479
489
|
|
480
490
|
return job
|
@@ -95,8 +95,11 @@ async def _get_workflow_check_owner(
|
|
95
95
|
project = await _get_project_check_owner(
|
96
96
|
project_id=project_id, user_id=user_id, db=db
|
97
97
|
)
|
98
|
+
|
98
99
|
# Get workflow
|
99
|
-
|
100
|
+
# (See issue 1087 for 'populate_existing=True')
|
101
|
+
workflow = await db.get(WorkflowV2, workflow_id, populate_existing=True)
|
102
|
+
|
100
103
|
if not workflow:
|
101
104
|
raise HTTPException(
|
102
105
|
status_code=status.HTTP_404_NOT_FOUND, detail="Workflow not found"
|
@@ -107,10 +110,6 @@ async def _get_workflow_check_owner(
|
|
107
110
|
detail=(f"Invalid {project_id=} for {workflow_id=}."),
|
108
111
|
)
|
109
112
|
|
110
|
-
# Refresh so that workflow.project relationship is loaded (see discussion
|
111
|
-
# in issue #1063)
|
112
|
-
await db.refresh(workflow)
|
113
|
-
|
114
113
|
return workflow
|
115
114
|
|
116
115
|
|
@@ -255,13 +254,15 @@ async def _get_dataset_check_owner(
|
|
255
254
|
HTTPException(status_code=422_UNPROCESSABLE_ENTITY):
|
256
255
|
If the dataset is not associated to the project
|
257
256
|
"""
|
258
|
-
|
259
257
|
# Access control for project
|
260
258
|
project = await _get_project_check_owner(
|
261
259
|
project_id=project_id, user_id=user_id, db=db
|
262
260
|
)
|
261
|
+
|
263
262
|
# Get dataset
|
264
|
-
|
263
|
+
# (See issue 1087 for 'populate_existing=True')
|
264
|
+
dataset = await db.get(DatasetV2, dataset_id, populate_existing=True)
|
265
|
+
|
265
266
|
if not dataset:
|
266
267
|
raise HTTPException(
|
267
268
|
status_code=status.HTTP_404_NOT_FOUND, detail="Dataset not found"
|
@@ -272,10 +273,6 @@ async def _get_dataset_check_owner(
|
|
272
273
|
detail=f"Invalid {project_id=} for {dataset_id=}",
|
273
274
|
)
|
274
275
|
|
275
|
-
# Refresh so that dataset.project relationship is loaded (see discussion
|
276
|
-
# in issue #1063)
|
277
|
-
await db.refresh(dataset)
|
278
|
-
|
279
276
|
return dict(dataset=dataset, project=project)
|
280
277
|
|
281
278
|
|
@@ -497,6 +494,32 @@ async def _workflow_insert_task(
|
|
497
494
|
db_workflow.task_list.reorder() # type: ignore
|
498
495
|
flag_modified(db_workflow, "task_list")
|
499
496
|
await db.commit()
|
500
|
-
|
497
|
+
|
498
|
+
# See issue 1087 for 'populate_existing=True'
|
499
|
+
wf_task = await db.get(WorkflowTaskV2, wf_task.id, populate_existing=True)
|
501
500
|
|
502
501
|
return wf_task
|
502
|
+
|
503
|
+
|
504
|
+
async def clean_app_job_list_v2(
|
505
|
+
db: AsyncSession, jobs_list: list[int]
|
506
|
+
) -> list[int]:
|
507
|
+
"""
|
508
|
+
Remove from a job list all jobs with status different from submitted.
|
509
|
+
|
510
|
+
Args:
|
511
|
+
db: Async database session
|
512
|
+
jobs_list: List of job IDs currently associated to the app.
|
513
|
+
|
514
|
+
Return:
|
515
|
+
List of IDs for submitted jobs.
|
516
|
+
"""
|
517
|
+
stmt = select(JobV2).where(JobV2.id.in_(jobs_list))
|
518
|
+
result = await db.execute(stmt)
|
519
|
+
db_jobs_list = result.scalars().all()
|
520
|
+
submitted_job_ids = [
|
521
|
+
job.id
|
522
|
+
for job in db_jobs_list
|
523
|
+
if job.status == JobStatusTypeV2.SUBMITTED
|
524
|
+
]
|
525
|
+
return submitted_job_ids
|
@@ -19,7 +19,7 @@ from ....security import current_active_user
|
|
19
19
|
from ....security import User
|
20
20
|
from ...aux._job import _write_shutdown_file
|
21
21
|
from ...aux._job import _zip_folder_to_byte_stream
|
22
|
-
from ...aux._runner import
|
22
|
+
from ...aux._runner import _is_shutdown_available
|
23
23
|
from ._aux_functions import _get_job_check_owner
|
24
24
|
from ._aux_functions import _get_project_check_owner
|
25
25
|
from ._aux_functions import _get_workflow_check_owner
|
@@ -180,11 +180,10 @@ async def stop_job(
|
|
180
180
|
db: AsyncSession = Depends(get_async_db),
|
181
181
|
) -> Response:
|
182
182
|
"""
|
183
|
-
Stop execution of a workflow job
|
183
|
+
Stop execution of a workflow job.
|
184
184
|
"""
|
185
185
|
|
186
|
-
|
187
|
-
_check_backend_is_slurm()
|
186
|
+
_is_shutdown_available()
|
188
187
|
|
189
188
|
# Get job from DB
|
190
189
|
output = await _get_job_check_owner(
|
@@ -7,6 +7,8 @@ from fastapi import Response
|
|
7
7
|
from fastapi import status
|
8
8
|
from sqlmodel import select
|
9
9
|
|
10
|
+
from .....logger import reset_logger_handlers
|
11
|
+
from .....logger import set_logger
|
10
12
|
from ....db import AsyncSession
|
11
13
|
from ....db import get_async_db
|
12
14
|
from ....models.v2 import DatasetV2
|
@@ -122,9 +124,11 @@ async def delete_project(
|
|
122
124
|
"""
|
123
125
|
Delete project
|
124
126
|
"""
|
127
|
+
|
125
128
|
project = await _get_project_check_owner(
|
126
129
|
project_id=project_id, user_id=user.id, db=db
|
127
130
|
)
|
131
|
+
logger = set_logger(__name__)
|
128
132
|
|
129
133
|
# Fail if there exist jobs that are submitted and in relation with the
|
130
134
|
# current project.
|
@@ -147,6 +151,7 @@ async def delete_project(
|
|
147
151
|
stm = select(WorkflowV2).where(WorkflowV2.project_id == project_id)
|
148
152
|
res = await db.execute(stm)
|
149
153
|
workflows = res.scalars().all()
|
154
|
+
logger.info("Start of cascade operations on Workflows.")
|
150
155
|
for wf in workflows:
|
151
156
|
# Cascade operations: set foreign-keys to null for jobs which are in
|
152
157
|
# relationship with the current workflow
|
@@ -154,14 +159,18 @@ async def delete_project(
|
|
154
159
|
res = await db.execute(stm)
|
155
160
|
jobs = res.scalars().all()
|
156
161
|
for job in jobs:
|
162
|
+
logger.info(f"Setting Job[{job.id}].workflow_id to None.")
|
157
163
|
job.workflow_id = None
|
158
164
|
# Delete workflow
|
165
|
+
logger.info(f"Adding Workflow[{wf.id}] to deletion.")
|
159
166
|
await db.delete(wf)
|
167
|
+
logger.info("End of cascade operations on Workflows.")
|
160
168
|
|
161
169
|
# Dataset
|
162
170
|
stm = select(DatasetV2).where(DatasetV2.project_id == project_id)
|
163
171
|
res = await db.execute(stm)
|
164
172
|
datasets = res.scalars().all()
|
173
|
+
logger.info("Start of cascade operations on Datasets.")
|
165
174
|
for ds in datasets:
|
166
175
|
# Cascade operations: set foreign-keys to null for jobs which are in
|
167
176
|
# relationship with the current dataset
|
@@ -169,18 +178,30 @@ async def delete_project(
|
|
169
178
|
res = await db.execute(stm)
|
170
179
|
jobs = res.scalars().all()
|
171
180
|
for job in jobs:
|
181
|
+
logger.info(f"Setting Job[{job.id}].dataset_id to None.")
|
172
182
|
job.dataset_id = None
|
173
183
|
# Delete dataset
|
184
|
+
logger.info(f"Adding Dataset[{ds.id}] to deletion.")
|
174
185
|
await db.delete(ds)
|
186
|
+
logger.info("End of cascade operations on Datasets.")
|
175
187
|
|
176
188
|
# Job
|
189
|
+
logger.info("Start of cascade operations on Jobs.")
|
177
190
|
stm = select(JobV2).where(JobV2.project_id == project_id)
|
178
191
|
res = await db.execute(stm)
|
179
192
|
jobs = res.scalars().all()
|
180
193
|
for job in jobs:
|
194
|
+
logger.info(f"Setting Job[{job.id}].project_id to None.")
|
181
195
|
job.project_id = None
|
196
|
+
logger.info("End of cascade operations on Jobs.")
|
182
197
|
|
198
|
+
logger.info(f"Adding Project[{project.id}] to deletion.")
|
183
199
|
await db.delete(project)
|
200
|
+
|
201
|
+
logger.info("Committing changes to db...")
|
184
202
|
await db.commit()
|
185
203
|
|
204
|
+
logger.info("Everything has been deleted correctly.")
|
205
|
+
reset_logger_handlers(logger)
|
206
|
+
|
186
207
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import os
|
1
2
|
from datetime import datetime
|
2
3
|
from datetime import timedelta
|
3
4
|
from datetime import timezone
|
@@ -8,10 +9,12 @@ from fastapi import APIRouter
|
|
8
9
|
from fastapi import BackgroundTasks
|
9
10
|
from fastapi import Depends
|
10
11
|
from fastapi import HTTPException
|
12
|
+
from fastapi import Request
|
11
13
|
from fastapi import status
|
12
14
|
from sqlmodel import select
|
13
15
|
|
14
16
|
from .....config import get_settings
|
17
|
+
from .....logger import set_logger
|
15
18
|
from .....syringe import Inject
|
16
19
|
from .....utils import get_timestamp
|
17
20
|
from ....db import AsyncSession
|
@@ -28,6 +31,7 @@ from ....security import current_active_verified_user
|
|
28
31
|
from ....security import User
|
29
32
|
from ._aux_functions import _get_dataset_check_owner
|
30
33
|
from ._aux_functions import _get_workflow_check_owner
|
34
|
+
from ._aux_functions import clean_app_job_list_v2
|
31
35
|
|
32
36
|
|
33
37
|
def _encode_as_utc(dt: datetime):
|
@@ -35,6 +39,7 @@ def _encode_as_utc(dt: datetime):
|
|
35
39
|
|
36
40
|
|
37
41
|
router = APIRouter()
|
42
|
+
logger = set_logger(__name__)
|
38
43
|
|
39
44
|
|
40
45
|
@router.post(
|
@@ -48,10 +53,23 @@ async def apply_workflow(
|
|
48
53
|
dataset_id: int,
|
49
54
|
job_create: JobCreateV2,
|
50
55
|
background_tasks: BackgroundTasks,
|
56
|
+
request: Request,
|
51
57
|
user: User = Depends(current_active_verified_user),
|
52
58
|
db: AsyncSession = Depends(get_async_db),
|
53
59
|
) -> Optional[JobReadV2]:
|
54
60
|
|
61
|
+
# Remove non-submitted V2 jobs from the app state when the list grows
|
62
|
+
# beyond a threshold
|
63
|
+
settings = Inject(get_settings)
|
64
|
+
if (
|
65
|
+
len(request.app.state.jobsV2)
|
66
|
+
> settings.FRACTAL_API_MAX_JOB_LIST_LENGTH
|
67
|
+
):
|
68
|
+
new_jobs_list = await clean_app_job_list_v2(
|
69
|
+
db, request.app.state.jobsV2
|
70
|
+
)
|
71
|
+
request.app.state.jobsV2 = new_jobs_list
|
72
|
+
|
55
73
|
output = await _get_dataset_check_owner(
|
56
74
|
project_id=project_id,
|
57
75
|
dataset_id=dataset_id,
|
@@ -92,7 +110,6 @@ async def apply_workflow(
|
|
92
110
|
)
|
93
111
|
|
94
112
|
# If backend is SLURM, check that the user has required attributes
|
95
|
-
settings = Inject(get_settings)
|
96
113
|
FRACTAL_RUNNER_BACKEND = settings.FRACTAL_RUNNER_BACKEND
|
97
114
|
if FRACTAL_RUNNER_BACKEND == "slurm":
|
98
115
|
if not user.slurm_user:
|
@@ -195,25 +212,24 @@ async def apply_workflow(
|
|
195
212
|
|
196
213
|
# Define server-side job directory
|
197
214
|
timestamp_string = get_timestamp().strftime("%Y%m%d_%H%M%S")
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
f"_{timestamp_string}"
|
203
|
-
)
|
204
|
-
).resolve()
|
215
|
+
WORKFLOW_DIR_LOCAL = settings.FRACTAL_RUNNER_WORKING_BASE_DIR / (
|
216
|
+
f"proj_v2_{project_id:07d}_wf_{workflow_id:07d}_job_{job.id:07d}"
|
217
|
+
f"_{timestamp_string}"
|
218
|
+
)
|
205
219
|
|
206
220
|
# Define user-side job directory
|
207
221
|
if FRACTAL_RUNNER_BACKEND == "local":
|
208
|
-
|
222
|
+
WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
|
223
|
+
elif FRACTAL_RUNNER_BACKEND == "local_experimental":
|
224
|
+
WORKFLOW_DIR_REMOTE = WORKFLOW_DIR_LOCAL
|
209
225
|
elif FRACTAL_RUNNER_BACKEND == "slurm":
|
210
|
-
|
211
|
-
Path(user.cache_dir) / f"{
|
212
|
-
)
|
226
|
+
WORKFLOW_DIR_REMOTE = (
|
227
|
+
Path(user.cache_dir) / f"{WORKFLOW_DIR_LOCAL.name}"
|
228
|
+
)
|
213
229
|
|
214
230
|
# Update job folders in the db
|
215
|
-
job.working_dir =
|
216
|
-
job.working_dir_user =
|
231
|
+
job.working_dir = WORKFLOW_DIR_LOCAL.as_posix()
|
232
|
+
job.working_dir_user = WORKFLOW_DIR_REMOTE.as_posix()
|
217
233
|
await db.merge(job)
|
218
234
|
await db.commit()
|
219
235
|
|
@@ -226,6 +242,11 @@ async def apply_workflow(
|
|
226
242
|
slurm_user=user.slurm_user,
|
227
243
|
user_cache_dir=user.cache_dir,
|
228
244
|
)
|
229
|
-
|
245
|
+
request.app.state.jobsV2.append(job.id)
|
246
|
+
logger.info(
|
247
|
+
f"Current worker's pid is {os.getpid()}. "
|
248
|
+
f"Current status of worker job's list "
|
249
|
+
f"{request.app.state.jobsV2}"
|
250
|
+
)
|
230
251
|
await db.close()
|
231
252
|
return job
|
@@ -1,13 +1,15 @@
|
|
1
1
|
from io import BytesIO
|
2
2
|
from pathlib import Path
|
3
|
+
from typing import Union
|
3
4
|
from zipfile import ZIP_DEFLATED
|
4
5
|
from zipfile import ZipFile
|
5
6
|
|
6
7
|
from ...models.v1 import ApplyWorkflow
|
8
|
+
from ...models.v2 import JobV2
|
7
9
|
from ...runner.filenames import SHUTDOWN_FILENAME
|
8
10
|
|
9
11
|
|
10
|
-
def _write_shutdown_file(*, job: ApplyWorkflow):
|
12
|
+
def _write_shutdown_file(*, job: Union[ApplyWorkflow, JobV2]):
|
11
13
|
"""
|
12
14
|
Write job's shutdown file.
|
13
15
|
|
@@ -5,15 +5,15 @@ from ....config import get_settings
|
|
5
5
|
from ....syringe import Inject
|
6
6
|
|
7
7
|
|
8
|
-
def
|
8
|
+
def _is_shutdown_available():
|
9
9
|
"""
|
10
10
|
Raises:
|
11
11
|
HTTPException(status_code=HTTP_422_UNPROCESSABLE_ENTITY):
|
12
|
-
If FRACTAL_RUNNER_BACKEND is
|
12
|
+
If FRACTAL_RUNNER_BACKEND is the thread-based 'local' backend.
|
13
13
|
"""
|
14
14
|
settings = Inject(get_settings)
|
15
15
|
backend = settings.FRACTAL_RUNNER_BACKEND
|
16
|
-
if backend
|
16
|
+
if backend not in ["slurm", "local_experimental"]:
|
17
17
|
raise HTTPException(
|
18
18
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
19
19
|
detail=(
|