fractal-server 2.16.6__py3-none-any.whl → 2.17.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/__main__.py +129 -22
- fractal_server/app/db/__init__.py +9 -11
- fractal_server/app/models/security.py +7 -3
- fractal_server/app/models/user_settings.py +0 -4
- fractal_server/app/models/v2/__init__.py +4 -0
- fractal_server/app/models/v2/profile.py +16 -0
- fractal_server/app/models/v2/project.py +3 -0
- fractal_server/app/models/v2/resource.py +130 -0
- fractal_server/app/models/v2/task_group.py +3 -0
- fractal_server/app/routes/admin/v2/__init__.py +4 -0
- fractal_server/app/routes/admin/v2/_aux_functions.py +55 -0
- fractal_server/app/routes/admin/v2/profile.py +86 -0
- fractal_server/app/routes/admin/v2/resource.py +229 -0
- fractal_server/app/routes/admin/v2/task_group_lifecycle.py +48 -82
- fractal_server/app/routes/api/__init__.py +26 -7
- fractal_server/app/routes/api/v2/_aux_functions.py +27 -1
- fractal_server/app/routes/api/v2/_aux_functions_history.py +2 -2
- fractal_server/app/routes/api/v2/_aux_functions_task_lifecycle.py +3 -3
- fractal_server/app/routes/api/v2/_aux_functions_tasks.py +7 -7
- fractal_server/app/routes/api/v2/project.py +5 -1
- fractal_server/app/routes/api/v2/submit.py +32 -24
- fractal_server/app/routes/api/v2/task.py +5 -0
- fractal_server/app/routes/api/v2/task_collection.py +36 -47
- fractal_server/app/routes/api/v2/task_collection_custom.py +11 -5
- fractal_server/app/routes/api/v2/task_collection_pixi.py +34 -40
- fractal_server/app/routes/api/v2/task_group_lifecycle.py +39 -82
- fractal_server/app/routes/auth/_aux_auth.py +3 -3
- fractal_server/app/routes/auth/current_user.py +45 -7
- fractal_server/app/routes/auth/oauth.py +1 -1
- fractal_server/app/routes/auth/users.py +9 -0
- fractal_server/app/routes/aux/_runner.py +2 -1
- fractal_server/app/routes/aux/validate_user_profile.py +62 -0
- fractal_server/app/routes/aux/validate_user_settings.py +12 -9
- fractal_server/app/schemas/user.py +20 -13
- fractal_server/app/schemas/user_settings.py +0 -4
- fractal_server/app/schemas/v2/__init__.py +11 -0
- fractal_server/app/schemas/v2/profile.py +72 -0
- fractal_server/app/schemas/v2/resource.py +117 -0
- fractal_server/app/security/__init__.py +6 -13
- fractal_server/app/security/signup_email.py +2 -2
- fractal_server/app/user_settings.py +2 -12
- fractal_server/config/__init__.py +23 -0
- fractal_server/config/_database.py +58 -0
- fractal_server/config/_email.py +170 -0
- fractal_server/config/_init_data.py +27 -0
- fractal_server/config/_main.py +216 -0
- fractal_server/config/_settings_config.py +7 -0
- fractal_server/images/tools.py +3 -3
- fractal_server/logger.py +3 -3
- fractal_server/main.py +14 -21
- fractal_server/migrations/versions/90f6508c6379_drop_useroauth_username.py +36 -0
- fractal_server/migrations/versions/a80ac5a352bf_resource_profile.py +195 -0
- fractal_server/runner/config/__init__.py +2 -0
- fractal_server/runner/config/_local.py +21 -0
- fractal_server/runner/config/_slurm.py +128 -0
- fractal_server/runner/config/slurm_mem_to_MB.py +63 -0
- fractal_server/runner/exceptions.py +4 -0
- fractal_server/runner/executors/base_runner.py +17 -7
- fractal_server/runner/executors/local/get_local_config.py +21 -86
- fractal_server/runner/executors/local/runner.py +48 -5
- fractal_server/runner/executors/slurm_common/_batching.py +2 -2
- fractal_server/runner/executors/slurm_common/base_slurm_runner.py +59 -25
- fractal_server/runner/executors/slurm_common/get_slurm_config.py +38 -54
- fractal_server/runner/executors/slurm_common/remote.py +1 -1
- fractal_server/runner/executors/slurm_common/{_slurm_config.py → slurm_config.py} +3 -254
- fractal_server/runner/executors/slurm_common/slurm_job_task_models.py +1 -1
- fractal_server/runner/executors/slurm_ssh/runner.py +12 -14
- fractal_server/runner/executors/slurm_sudo/_subprocess_run_as_user.py +2 -2
- fractal_server/runner/executors/slurm_sudo/runner.py +12 -12
- fractal_server/runner/v2/_local.py +36 -21
- fractal_server/runner/v2/_slurm_ssh.py +40 -4
- fractal_server/runner/v2/_slurm_sudo.py +41 -11
- fractal_server/runner/v2/db_tools.py +1 -1
- fractal_server/runner/v2/runner.py +3 -11
- fractal_server/runner/v2/runner_functions.py +42 -28
- fractal_server/runner/v2/submit_workflow.py +87 -108
- fractal_server/runner/versions.py +8 -3
- fractal_server/ssh/_fabric.py +6 -6
- fractal_server/tasks/config/__init__.py +3 -0
- fractal_server/tasks/config/_pixi.py +127 -0
- fractal_server/tasks/config/_python.py +51 -0
- fractal_server/tasks/v2/local/_utils.py +7 -7
- fractal_server/tasks/v2/local/collect.py +13 -5
- fractal_server/tasks/v2/local/collect_pixi.py +26 -10
- fractal_server/tasks/v2/local/deactivate.py +7 -1
- fractal_server/tasks/v2/local/deactivate_pixi.py +5 -1
- fractal_server/tasks/v2/local/delete.py +4 -0
- fractal_server/tasks/v2/local/reactivate.py +13 -5
- fractal_server/tasks/v2/local/reactivate_pixi.py +27 -9
- fractal_server/tasks/v2/ssh/_pixi_slurm_ssh.py +11 -10
- fractal_server/tasks/v2/ssh/_utils.py +6 -7
- fractal_server/tasks/v2/ssh/collect.py +19 -12
- fractal_server/tasks/v2/ssh/collect_pixi.py +34 -16
- fractal_server/tasks/v2/ssh/deactivate.py +12 -8
- fractal_server/tasks/v2/ssh/deactivate_pixi.py +14 -10
- fractal_server/tasks/v2/ssh/delete.py +12 -9
- fractal_server/tasks/v2/ssh/reactivate.py +18 -12
- fractal_server/tasks/v2/ssh/reactivate_pixi.py +36 -17
- fractal_server/tasks/v2/templates/4_pip_show.sh +4 -6
- fractal_server/tasks/v2/utils_database.py +2 -2
- fractal_server/tasks/v2/utils_python_interpreter.py +8 -16
- fractal_server/tasks/v2/utils_templates.py +7 -10
- fractal_server/utils.py +1 -1
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/METADATA +1 -1
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/RECORD +110 -88
- fractal_server/config.py +0 -906
- /fractal_server/{runner → app}/shutdown.py +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/WHEEL +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/entry_points.txt +0 -0
- {fractal_server-2.16.6.dist-info → fractal_server-2.17.0a0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from fastapi import Depends
|
|
3
|
+
from fastapi import HTTPException
|
|
4
|
+
from fastapi import Response
|
|
5
|
+
from fastapi import status
|
|
6
|
+
from sqlmodel import func
|
|
7
|
+
from sqlmodel import select
|
|
8
|
+
|
|
9
|
+
from ._aux_functions import _check_resource_name
|
|
10
|
+
from ._aux_functions import _get_resource_or_404
|
|
11
|
+
from .profile import _check_profile_name
|
|
12
|
+
from fractal_server.app.db import AsyncSession
|
|
13
|
+
from fractal_server.app.db import get_async_db
|
|
14
|
+
from fractal_server.app.models import UserOAuth
|
|
15
|
+
from fractal_server.app.models.v2 import Profile
|
|
16
|
+
from fractal_server.app.models.v2 import Resource
|
|
17
|
+
from fractal_server.app.routes.auth import current_active_superuser
|
|
18
|
+
from fractal_server.app.schemas.v2 import ProfileCreate
|
|
19
|
+
from fractal_server.app.schemas.v2 import ProfileRead
|
|
20
|
+
from fractal_server.app.schemas.v2 import ResourceCreate
|
|
21
|
+
from fractal_server.app.schemas.v2 import ResourceRead
|
|
22
|
+
from fractal_server.config import get_settings
|
|
23
|
+
from fractal_server.syringe import Inject
|
|
24
|
+
|
|
25
|
+
router = APIRouter()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _check_resource_type_match_or_422(
|
|
29
|
+
resource: Resource,
|
|
30
|
+
new_profile: ProfileCreate,
|
|
31
|
+
) -> None:
|
|
32
|
+
if resource.type != new_profile.resource_type:
|
|
33
|
+
raise HTTPException(
|
|
34
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
35
|
+
detail=(
|
|
36
|
+
f"{resource.type=} differs from {new_profile.resource_type=}."
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _check_type_match_or_422(new_resource: ResourceCreate) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Handle case where `resource.type != FRACTAL_RUNNER_BACKEND`
|
|
44
|
+
"""
|
|
45
|
+
settings = Inject(get_settings)
|
|
46
|
+
if settings.FRACTAL_RUNNER_BACKEND != new_resource.type:
|
|
47
|
+
raise HTTPException(
|
|
48
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
49
|
+
detail=(
|
|
50
|
+
f"{settings.FRACTAL_RUNNER_BACKEND=} != "
|
|
51
|
+
f"{new_resource.type=}"
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@router.get("/", response_model=list[ResourceRead], status_code=200)
|
|
57
|
+
async def get_resource_list(
|
|
58
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
59
|
+
db: AsyncSession = Depends(get_async_db),
|
|
60
|
+
) -> list[ResourceRead]:
|
|
61
|
+
"""
|
|
62
|
+
Query `Resource` table.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
stm = select(Resource)
|
|
66
|
+
res = await db.execute(stm)
|
|
67
|
+
resource_list = res.scalars().all()
|
|
68
|
+
|
|
69
|
+
return resource_list
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@router.get("/{resource_id}/", response_model=ResourceRead, status_code=200)
|
|
73
|
+
async def get_resource(
|
|
74
|
+
resource_id: int,
|
|
75
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
76
|
+
db: AsyncSession = Depends(get_async_db),
|
|
77
|
+
) -> ResourceRead:
|
|
78
|
+
"""
|
|
79
|
+
Query single `Resource`.
|
|
80
|
+
"""
|
|
81
|
+
resource = await _get_resource_or_404(resource_id=resource_id, db=db)
|
|
82
|
+
|
|
83
|
+
return resource
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@router.post("/", response_model=ResourceRead, status_code=201)
|
|
87
|
+
async def post_resource(
|
|
88
|
+
resource_create: ResourceCreate,
|
|
89
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
90
|
+
db: AsyncSession = Depends(get_async_db),
|
|
91
|
+
) -> ResourceRead:
|
|
92
|
+
"""
|
|
93
|
+
Create new `Resource`.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# Handle case where type!=FRACTAL_RUNNER_BACKEND
|
|
97
|
+
_check_type_match_or_422(resource_create)
|
|
98
|
+
|
|
99
|
+
await _check_resource_name(name=resource_create.name, db=db)
|
|
100
|
+
|
|
101
|
+
resource = Resource(**resource_create.model_dump())
|
|
102
|
+
db.add(resource)
|
|
103
|
+
await db.commit()
|
|
104
|
+
await db.refresh(resource)
|
|
105
|
+
|
|
106
|
+
return resource
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@router.put(
|
|
110
|
+
"/{resource_id}/",
|
|
111
|
+
response_model=ResourceRead,
|
|
112
|
+
status_code=200,
|
|
113
|
+
)
|
|
114
|
+
async def put_resource(
|
|
115
|
+
resource_id: int,
|
|
116
|
+
resource_update: ResourceCreate,
|
|
117
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
118
|
+
db: AsyncSession = Depends(get_async_db),
|
|
119
|
+
) -> ResourceRead:
|
|
120
|
+
"""
|
|
121
|
+
Overwrite a single `Resource`.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
# Handle case where type!=FRACTAL_RUNNER_BACKEND
|
|
125
|
+
_check_type_match_or_422(resource_update)
|
|
126
|
+
|
|
127
|
+
resource = await _get_resource_or_404(resource_id=resource_id, db=db)
|
|
128
|
+
|
|
129
|
+
# Handle non-unique resource names
|
|
130
|
+
if resource_update.name and resource_update.name != resource.name:
|
|
131
|
+
await _check_resource_name(name=resource_update.name, db=db)
|
|
132
|
+
|
|
133
|
+
# Prepare new db object
|
|
134
|
+
for key, value in resource_update.model_dump().items():
|
|
135
|
+
setattr(resource, key, value)
|
|
136
|
+
|
|
137
|
+
await db.commit()
|
|
138
|
+
await db.refresh(resource)
|
|
139
|
+
return resource
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@router.delete("/{resource_id}/", status_code=204)
|
|
143
|
+
async def delete_resource(
|
|
144
|
+
resource_id: int,
|
|
145
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
146
|
+
db: AsyncSession = Depends(get_async_db),
|
|
147
|
+
):
|
|
148
|
+
"""
|
|
149
|
+
Delete single `Resource`.
|
|
150
|
+
"""
|
|
151
|
+
resource = await _get_resource_or_404(resource_id=resource_id, db=db)
|
|
152
|
+
|
|
153
|
+
# Fail if at least one Profile is associated with the Resource.
|
|
154
|
+
res = await db.execute(
|
|
155
|
+
select(func.count(Profile.id)).where(
|
|
156
|
+
Profile.resource_id == resource_id
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
associated_profile_count = res.scalar()
|
|
160
|
+
if associated_profile_count > 0:
|
|
161
|
+
raise HTTPException(
|
|
162
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
163
|
+
detail=(
|
|
164
|
+
f"Cannot delete Resource {resource_id} because it's associated"
|
|
165
|
+
f" with {associated_profile_count} Profiles."
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Delete
|
|
170
|
+
await db.delete(resource)
|
|
171
|
+
await db.commit()
|
|
172
|
+
|
|
173
|
+
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@router.get(
|
|
177
|
+
"/{resource_id}/profile/",
|
|
178
|
+
response_model=list[ProfileRead],
|
|
179
|
+
status_code=200,
|
|
180
|
+
)
|
|
181
|
+
async def get_resource_profiles(
|
|
182
|
+
resource_id: int,
|
|
183
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
184
|
+
db: AsyncSession = Depends(get_async_db),
|
|
185
|
+
) -> list[ProfileRead]:
|
|
186
|
+
"""
|
|
187
|
+
Query `Profile`s for single `Resource`.
|
|
188
|
+
"""
|
|
189
|
+
await _get_resource_or_404(resource_id=resource_id, db=db)
|
|
190
|
+
|
|
191
|
+
res = await db.execute(
|
|
192
|
+
select(Profile).where(Profile.resource_id == resource_id)
|
|
193
|
+
)
|
|
194
|
+
profiles = res.scalars().all()
|
|
195
|
+
|
|
196
|
+
return profiles
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@router.post(
|
|
200
|
+
"/{resource_id}/profile/",
|
|
201
|
+
response_model=ProfileRead,
|
|
202
|
+
status_code=201,
|
|
203
|
+
)
|
|
204
|
+
async def post_profile(
|
|
205
|
+
resource_id: int,
|
|
206
|
+
profile_create: ProfileCreate,
|
|
207
|
+
superuser: UserOAuth = Depends(current_active_superuser),
|
|
208
|
+
db: AsyncSession = Depends(get_async_db),
|
|
209
|
+
) -> ProfileRead:
|
|
210
|
+
"""
|
|
211
|
+
Create new `Profile`.
|
|
212
|
+
"""
|
|
213
|
+
resource = await _get_resource_or_404(resource_id=resource_id, db=db)
|
|
214
|
+
|
|
215
|
+
_check_resource_type_match_or_422(
|
|
216
|
+
resource=resource,
|
|
217
|
+
new_profile=profile_create,
|
|
218
|
+
)
|
|
219
|
+
await _check_profile_name(name=profile_create.name, db=db)
|
|
220
|
+
|
|
221
|
+
profile = Profile(
|
|
222
|
+
resource_id=resource_id,
|
|
223
|
+
**profile_create.model_dump(),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
db.add(profile)
|
|
227
|
+
await db.commit()
|
|
228
|
+
await db.refresh(profile)
|
|
229
|
+
return profile
|
|
@@ -22,17 +22,15 @@ from fractal_server.app.routes.api.v2._aux_functions_tasks import (
|
|
|
22
22
|
_get_task_group_or_404,
|
|
23
23
|
)
|
|
24
24
|
from fractal_server.app.routes.auth import current_active_superuser
|
|
25
|
-
from fractal_server.app.routes.aux.
|
|
26
|
-
|
|
25
|
+
from fractal_server.app.routes.aux.validate_user_profile import (
|
|
26
|
+
validate_user_profile,
|
|
27
27
|
)
|
|
28
|
+
from fractal_server.app.schemas.v2 import ResourceType
|
|
28
29
|
from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
|
|
29
30
|
from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
|
|
30
31
|
from fractal_server.app.schemas.v2 import TaskGroupActivityV2Read
|
|
31
32
|
from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
|
|
32
|
-
from fractal_server.config import get_settings
|
|
33
33
|
from fractal_server.logger import set_logger
|
|
34
|
-
from fractal_server.ssh._fabric import SSHConfig
|
|
35
|
-
from fractal_server.syringe import Inject
|
|
36
34
|
from fractal_server.tasks.v2.local import deactivate_local
|
|
37
35
|
from fractal_server.tasks.v2.local import delete_local
|
|
38
36
|
from fractal_server.tasks.v2.local import reactivate_local
|
|
@@ -114,34 +112,23 @@ async def deactivate_task_group(
|
|
|
114
112
|
db.add(task_group_activity)
|
|
115
113
|
await db.commit()
|
|
116
114
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# Validate user settings (backend-specific)
|
|
121
|
-
user = await db.get(UserOAuth, task_group.user_id)
|
|
122
|
-
user_settings = await validate_user_settings(
|
|
123
|
-
user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
|
|
124
|
-
)
|
|
125
|
-
# User appropriate FractalSSH object
|
|
126
|
-
ssh_config = SSHConfig(
|
|
127
|
-
user=user_settings.ssh_username,
|
|
128
|
-
host=user_settings.ssh_host,
|
|
129
|
-
key_path=user_settings.ssh_private_key_path,
|
|
130
|
-
)
|
|
115
|
+
user = await db.get(UserOAuth, task_group.user_id)
|
|
116
|
+
# Get validated resource and profile
|
|
117
|
+
resource, profile = await validate_user_profile(user=user, db=db)
|
|
131
118
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
task_group_activity_id=task_group_activity.id,
|
|
136
|
-
ssh_config=ssh_config,
|
|
137
|
-
tasks_base_dir=user_settings.ssh_tasks_dir,
|
|
138
|
-
)
|
|
119
|
+
# Submit background task
|
|
120
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
121
|
+
deactivate_function = deactivate_ssh
|
|
139
122
|
else:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
123
|
+
deactivate_function = deactivate_local
|
|
124
|
+
|
|
125
|
+
background_tasks.add_task(
|
|
126
|
+
deactivate_function,
|
|
127
|
+
task_group_id=task_group.id,
|
|
128
|
+
task_group_activity_id=task_group_activity.id,
|
|
129
|
+
resource=resource,
|
|
130
|
+
profile=profile,
|
|
131
|
+
)
|
|
145
132
|
|
|
146
133
|
logger.debug(
|
|
147
134
|
"Admin task group deactivation endpoint: start deactivate "
|
|
@@ -229,34 +216,24 @@ async def reactivate_task_group(
|
|
|
229
216
|
db.add(task_group_activity)
|
|
230
217
|
await db.commit()
|
|
231
218
|
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
# Validate user settings (backend-specific)
|
|
236
|
-
user = await db.get(UserOAuth, task_group.user_id)
|
|
237
|
-
user_settings = await validate_user_settings(
|
|
238
|
-
user=user, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
|
|
239
|
-
)
|
|
240
|
-
# Use appropriate FractalSSH object
|
|
241
|
-
ssh_config = SSHConfig(
|
|
242
|
-
user=user_settings.ssh_username,
|
|
243
|
-
host=user_settings.ssh_host,
|
|
244
|
-
key_path=user_settings.ssh_private_key_path,
|
|
245
|
-
)
|
|
219
|
+
# Get validated resource and profile
|
|
220
|
+
user = await db.get(UserOAuth, task_group.user_id)
|
|
221
|
+
resource, profile = await validate_user_profile(user=user, db=db)
|
|
246
222
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
task_group_activity_id=task_group_activity.id,
|
|
251
|
-
ssh_config=ssh_config,
|
|
252
|
-
tasks_base_dir=user_settings.ssh_tasks_dir,
|
|
253
|
-
)
|
|
223
|
+
# Submit background task
|
|
224
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
225
|
+
reactivate_function = reactivate_ssh
|
|
254
226
|
else:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
227
|
+
reactivate_function = reactivate_local
|
|
228
|
+
|
|
229
|
+
background_tasks.add_task(
|
|
230
|
+
reactivate_function,
|
|
231
|
+
task_group_id=task_group.id,
|
|
232
|
+
task_group_activity_id=task_group_activity.id,
|
|
233
|
+
resource=resource,
|
|
234
|
+
profile=profile,
|
|
235
|
+
)
|
|
236
|
+
|
|
260
237
|
logger.debug(
|
|
261
238
|
"Admin task group reactivation endpoint: start reactivate "
|
|
262
239
|
"and return task_group_activity"
|
|
@@ -292,33 +269,22 @@ async def delete_task_group(
|
|
|
292
269
|
db.add(task_group_activity)
|
|
293
270
|
await db.commit()
|
|
294
271
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
task_owner = await db.get(UserOAuth, task_group.user_id)
|
|
299
|
-
task_owner_settings = await validate_user_settings(
|
|
300
|
-
user=task_owner, backend=settings.FRACTAL_RUNNER_BACKEND, db=db
|
|
301
|
-
)
|
|
302
|
-
# Use appropriate FractalSSH object
|
|
303
|
-
ssh_config = SSHConfig(
|
|
304
|
-
user=task_owner_settings.ssh_username,
|
|
305
|
-
host=task_owner_settings.ssh_host,
|
|
306
|
-
key_path=task_owner_settings.ssh_private_key_path,
|
|
307
|
-
)
|
|
272
|
+
# Get validated resource and profile
|
|
273
|
+
task_owner = await db.get(UserOAuth, task_group.user_id)
|
|
274
|
+
resource, profile = await validate_user_profile(user=task_owner, db=db)
|
|
308
275
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
task_group_id=task_group.id,
|
|
312
|
-
task_group_activity_id=task_group_activity.id,
|
|
313
|
-
ssh_config=ssh_config,
|
|
314
|
-
tasks_base_dir=task_owner_settings.ssh_tasks_dir,
|
|
315
|
-
)
|
|
276
|
+
if resource.type == ResourceType.SLURM_SSH:
|
|
277
|
+
delete_function = delete_ssh
|
|
316
278
|
else:
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
279
|
+
delete_function = delete_local
|
|
280
|
+
|
|
281
|
+
background_tasks.add_task(
|
|
282
|
+
delete_function,
|
|
283
|
+
task_group_activity_id=task_group_activity.id,
|
|
284
|
+
task_group_id=task_group.id,
|
|
285
|
+
resource=resource,
|
|
286
|
+
profile=profile,
|
|
287
|
+
)
|
|
322
288
|
logger.debug(
|
|
323
289
|
"Admin task group deletion endpoint: start deletion "
|
|
324
290
|
"and return task_group_activity"
|
|
@@ -4,25 +4,44 @@
|
|
|
4
4
|
from fastapi import APIRouter
|
|
5
5
|
from fastapi import Depends
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
from ....syringe import Inject
|
|
7
|
+
import fractal_server
|
|
9
8
|
from fractal_server.app.models import UserOAuth
|
|
10
9
|
from fractal_server.app.routes.auth import current_active_superuser
|
|
11
|
-
|
|
10
|
+
from fractal_server.config import get_db_settings
|
|
11
|
+
from fractal_server.config import get_email_settings
|
|
12
|
+
from fractal_server.config import get_settings
|
|
13
|
+
from fractal_server.syringe import Inject
|
|
12
14
|
|
|
13
15
|
router_api = APIRouter()
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
@router_api.get("/alive/")
|
|
17
19
|
async def alive():
|
|
18
|
-
settings = Inject(get_settings)
|
|
19
20
|
return dict(
|
|
20
21
|
alive=True,
|
|
21
|
-
version=
|
|
22
|
+
version=fractal_server.__VERSION__,
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
@router_api.get("/settings/")
|
|
26
|
-
async def view_settings(
|
|
26
|
+
@router_api.get("/settings/app/")
|
|
27
|
+
async def view_settings(
|
|
28
|
+
user: UserOAuth = Depends(current_active_superuser),
|
|
29
|
+
):
|
|
27
30
|
settings = Inject(get_settings)
|
|
28
31
|
return settings.model_dump()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router_api.get("/settings/database/")
|
|
35
|
+
async def view_db_settings(
|
|
36
|
+
user: UserOAuth = Depends(current_active_superuser),
|
|
37
|
+
):
|
|
38
|
+
settings = Inject(get_db_settings)
|
|
39
|
+
return settings.model_dump()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@router_api.get("/settings/email/")
|
|
43
|
+
async def view_email_settings(
|
|
44
|
+
user: UserOAuth = Depends(current_active_superuser),
|
|
45
|
+
):
|
|
46
|
+
settings = Inject(get_email_settings)
|
|
47
|
+
return settings.model_dump()
|
|
@@ -11,7 +11,6 @@ from sqlalchemy.orm.attributes import flag_modified
|
|
|
11
11
|
from sqlmodel import select
|
|
12
12
|
from sqlmodel.sql.expression import SelectOfScalar
|
|
13
13
|
|
|
14
|
-
from ....db import AsyncSession
|
|
15
14
|
from ....models.v2 import DatasetV2
|
|
16
15
|
from ....models.v2 import JobV2
|
|
17
16
|
from ....models.v2 import LinkUserProjectV2
|
|
@@ -20,6 +19,10 @@ from ....models.v2 import TaskV2
|
|
|
20
19
|
from ....models.v2 import WorkflowTaskV2
|
|
21
20
|
from ....models.v2 import WorkflowV2
|
|
22
21
|
from ....schemas.v2 import JobStatusTypeV2
|
|
22
|
+
from fractal_server.app.db import AsyncSession
|
|
23
|
+
from fractal_server.app.models import Profile
|
|
24
|
+
from fractal_server.app.models import Resource
|
|
25
|
+
from fractal_server.app.models import UserOAuth
|
|
23
26
|
from fractal_server.logger import set_logger
|
|
24
27
|
|
|
25
28
|
logger = set_logger(__name__)
|
|
@@ -538,3 +541,26 @@ async def _get_submitted_job_or_none(
|
|
|
538
541
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
539
542
|
detail=error_msg,
|
|
540
543
|
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
async def _get_resource_and_profile_ids(
|
|
547
|
+
*,
|
|
548
|
+
user_id: int,
|
|
549
|
+
db: AsyncSession,
|
|
550
|
+
) -> tuple[int, int] | tuple[None, None]:
|
|
551
|
+
"""
|
|
552
|
+
Get `(resource_id, profile_id)` pair for a given user, or `(None,None)`.
|
|
553
|
+
"""
|
|
554
|
+
stm = (
|
|
555
|
+
select(Resource.id, Profile.id)
|
|
556
|
+
.join(UserOAuth)
|
|
557
|
+
.where(Resource.id == Profile.resource_id)
|
|
558
|
+
.where(Profile.id == UserOAuth.profile_id)
|
|
559
|
+
.where(UserOAuth.id == user_id)
|
|
560
|
+
)
|
|
561
|
+
res = await db.execute(stm)
|
|
562
|
+
data = res.one_or_none()
|
|
563
|
+
if data is None:
|
|
564
|
+
return (None, None)
|
|
565
|
+
else:
|
|
566
|
+
return tuple(data)
|
|
@@ -32,7 +32,7 @@ async def get_history_unit_or_404(
|
|
|
32
32
|
"""
|
|
33
33
|
Get an existing HistoryUnit or raise a 404.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Args:
|
|
36
36
|
history_unit_id: The `HistoryUnit` id
|
|
37
37
|
db: An asynchronous db session
|
|
38
38
|
"""
|
|
@@ -51,7 +51,7 @@ async def get_history_run_or_404(
|
|
|
51
51
|
"""
|
|
52
52
|
Get an existing HistoryRun or raise a 404.
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Args:
|
|
55
55
|
history_run_id:
|
|
56
56
|
db:
|
|
57
57
|
"""
|
|
@@ -59,7 +59,7 @@ async def get_package_version_from_pypi(
|
|
|
59
59
|
|
|
60
60
|
Ref https://warehouse.pypa.io/api-reference/json.html.
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Args:
|
|
63
63
|
name: Package name.
|
|
64
64
|
version:
|
|
65
65
|
Could be a correct version (`1.3.0`), an incomplete one
|
|
@@ -165,7 +165,7 @@ async def check_no_ongoing_activity(
|
|
|
165
165
|
"""
|
|
166
166
|
Find ongoing activities for the same task group.
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
Args:
|
|
169
169
|
task_group_id:
|
|
170
170
|
db:
|
|
171
171
|
"""
|
|
@@ -204,7 +204,7 @@ async def check_no_submitted_job(
|
|
|
204
204
|
"""
|
|
205
205
|
Find submitted jobs which include tasks from a given task group.
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
Args:
|
|
208
208
|
task_group_id: ID of the `TaskGroupV2` object.
|
|
209
209
|
db: Asynchronous database session.
|
|
210
210
|
"""
|
|
@@ -33,7 +33,7 @@ async def _get_task_group_or_404(
|
|
|
33
33
|
"""
|
|
34
34
|
Get an existing task group or raise a 404.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Args:
|
|
37
37
|
task_group_id: The TaskGroupV2 id
|
|
38
38
|
db: An asynchronous db session
|
|
39
39
|
"""
|
|
@@ -55,7 +55,7 @@ async def _get_task_group_read_access(
|
|
|
55
55
|
"""
|
|
56
56
|
Get a task group or raise a 403 if user has no read access.
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Args:
|
|
59
59
|
task_group_id: ID of the required task group.
|
|
60
60
|
user_id: ID of the current user.
|
|
61
61
|
db: An asynchronous db session.
|
|
@@ -100,7 +100,7 @@ async def _get_task_group_full_access(
|
|
|
100
100
|
"""
|
|
101
101
|
Get a task group or raise a 403 if user has no full access.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
Args:
|
|
104
104
|
task_group_id: ID of the required task group.
|
|
105
105
|
user_id: ID of the current user.
|
|
106
106
|
db: An asynchronous db session
|
|
@@ -125,7 +125,7 @@ async def _get_task_or_404(*, task_id: int, db: AsyncSession) -> TaskV2:
|
|
|
125
125
|
"""
|
|
126
126
|
Get an existing task or raise a 404.
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Args:
|
|
129
129
|
task_id: ID of the required task.
|
|
130
130
|
db: An asynchronous db session
|
|
131
131
|
"""
|
|
@@ -147,7 +147,7 @@ async def _get_task_full_access(
|
|
|
147
147
|
"""
|
|
148
148
|
Get an existing task or raise a 404.
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
Args:
|
|
151
151
|
task_id: ID of the required task.
|
|
152
152
|
user_id: ID of the current user.
|
|
153
153
|
db: An asynchronous db session.
|
|
@@ -169,7 +169,7 @@ async def _get_task_read_access(
|
|
|
169
169
|
"""
|
|
170
170
|
Get an existing task or raise a 404.
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
Args:
|
|
173
173
|
task_id: ID of the required task.
|
|
174
174
|
user_id: ID of the current user.
|
|
175
175
|
db: An asynchronous db session.
|
|
@@ -198,7 +198,7 @@ async def _get_valid_user_group_id(
|
|
|
198
198
|
"""
|
|
199
199
|
Validate query parameters for endpoints that create some task(s).
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
Args:
|
|
202
202
|
user_group_id:
|
|
203
203
|
private:
|
|
204
204
|
user_id: ID of the current user
|
|
@@ -17,6 +17,7 @@ from ....schemas.v2 import ProjectReadV2
|
|
|
17
17
|
from ....schemas.v2 import ProjectUpdateV2
|
|
18
18
|
from ._aux_functions import _check_project_exists
|
|
19
19
|
from ._aux_functions import _get_project_check_owner
|
|
20
|
+
from ._aux_functions import _get_resource_and_profile_ids
|
|
20
21
|
from ._aux_functions import _get_submitted_jobs_statement
|
|
21
22
|
from fractal_server.app.models import UserOAuth
|
|
22
23
|
from fractal_server.app.routes.auth import current_active_user
|
|
@@ -57,8 +58,11 @@ async def create_project(
|
|
|
57
58
|
await _check_project_exists(
|
|
58
59
|
project_name=project.name, user_id=user.id, db=db
|
|
59
60
|
)
|
|
61
|
+
resource_id, _ = await _get_resource_and_profile_ids(
|
|
62
|
+
user_id=user.id, db=db
|
|
63
|
+
)
|
|
60
64
|
|
|
61
|
-
db_project = ProjectV2(**project.model_dump())
|
|
65
|
+
db_project = ProjectV2(**project.model_dump(), resource_id=resource_id)
|
|
62
66
|
db_project.user_list.append(user)
|
|
63
67
|
|
|
64
68
|
db.add(db_project)
|