fractal-server 2.14.16__py3-none-any.whl → 2.15.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 (54) hide show
  1. fractal_server/__init__.py +1 -1
  2. fractal_server/app/models/security.py +2 -2
  3. fractal_server/app/models/user_settings.py +2 -2
  4. fractal_server/app/models/v2/dataset.py +3 -3
  5. fractal_server/app/models/v2/job.py +6 -6
  6. fractal_server/app/models/v2/task.py +12 -8
  7. fractal_server/app/models/v2/task_group.py +19 -7
  8. fractal_server/app/models/v2/workflowtask.py +6 -6
  9. fractal_server/app/routes/admin/v2/task_group_lifecycle.py +2 -5
  10. fractal_server/app/routes/api/v2/__init__.py +6 -0
  11. fractal_server/app/routes/api/v2/_aux_functions_tasks.py +22 -0
  12. fractal_server/app/routes/api/v2/task_collection.py +8 -18
  13. fractal_server/app/routes/api/v2/task_collection_custom.py +2 -2
  14. fractal_server/app/routes/api/v2/task_collection_pixi.py +219 -0
  15. fractal_server/app/routes/api/v2/task_group.py +3 -0
  16. fractal_server/app/routes/api/v2/task_group_lifecycle.py +26 -10
  17. fractal_server/app/runner/executors/slurm_common/_slurm_config.py +10 -0
  18. fractal_server/app/runner/executors/slurm_common/base_slurm_runner.py +39 -14
  19. fractal_server/app/runner/executors/slurm_common/get_slurm_config.py +8 -1
  20. fractal_server/app/schemas/v2/__init__.py +1 -1
  21. fractal_server/app/schemas/v2/dumps.py +1 -1
  22. fractal_server/app/schemas/v2/task_collection.py +1 -1
  23. fractal_server/app/schemas/v2/task_group.py +7 -5
  24. fractal_server/config.py +70 -0
  25. fractal_server/migrations/versions/b1e7f7a1ff71_task_group_for_pixi.py +53 -0
  26. fractal_server/migrations/versions/b3ffb095f973_json_to_jsonb.py +340 -0
  27. fractal_server/ssh/_fabric.py +26 -0
  28. fractal_server/tasks/v2/local/__init__.py +3 -0
  29. fractal_server/tasks/v2/local/_utils.py +4 -3
  30. fractal_server/tasks/v2/local/collect.py +26 -30
  31. fractal_server/tasks/v2/local/collect_pixi.py +252 -0
  32. fractal_server/tasks/v2/local/deactivate.py +39 -46
  33. fractal_server/tasks/v2/local/deactivate_pixi.py +98 -0
  34. fractal_server/tasks/v2/local/reactivate.py +12 -23
  35. fractal_server/tasks/v2/local/reactivate_pixi.py +184 -0
  36. fractal_server/tasks/v2/ssh/__init__.py +3 -0
  37. fractal_server/tasks/v2/ssh/_utils.py +50 -9
  38. fractal_server/tasks/v2/ssh/collect.py +46 -56
  39. fractal_server/tasks/v2/ssh/collect_pixi.py +315 -0
  40. fractal_server/tasks/v2/ssh/deactivate.py +54 -67
  41. fractal_server/tasks/v2/ssh/deactivate_pixi.py +122 -0
  42. fractal_server/tasks/v2/ssh/reactivate.py +25 -38
  43. fractal_server/tasks/v2/ssh/reactivate_pixi.py +233 -0
  44. fractal_server/tasks/v2/templates/pixi_1_extract.sh +40 -0
  45. fractal_server/tasks/v2/templates/pixi_2_install.sh +52 -0
  46. fractal_server/tasks/v2/templates/pixi_3_post_install.sh +76 -0
  47. fractal_server/tasks/v2/utils_background.py +50 -8
  48. fractal_server/tasks/v2/utils_pixi.py +38 -0
  49. fractal_server/tasks/v2/utils_templates.py +14 -1
  50. {fractal_server-2.14.16.dist-info → fractal_server-2.15.0.dist-info}/METADATA +1 -1
  51. {fractal_server-2.14.16.dist-info → fractal_server-2.15.0.dist-info}/RECORD +54 -41
  52. {fractal_server-2.14.16.dist-info → fractal_server-2.15.0.dist-info}/LICENSE +0 -0
  53. {fractal_server-2.14.16.dist-info → fractal_server-2.15.0.dist-info}/WHEEL +0 -0
  54. {fractal_server-2.14.16.dist-info → fractal_server-2.15.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,315 @@
1
+ import time
2
+ from pathlib import Path
3
+ from tempfile import TemporaryDirectory
4
+
5
+ from ..utils_background import fail_and_cleanup
6
+ from ..utils_background import get_activity_and_task_group
7
+ from ..utils_background import prepare_tasks_metadata
8
+ from ..utils_database import create_db_tasks_and_update_task_group_sync
9
+ from ..utils_pixi import parse_collect_stdout
10
+ from ..utils_pixi import SOURCE_DIR_NAME
11
+ from ._utils import check_ssh_or_fail_and_cleanup
12
+ from fractal_server.app.db import get_sync_db
13
+ from fractal_server.app.schemas.v2 import FractalUploadedFile
14
+ from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
15
+ from fractal_server.app.schemas.v2 import TaskGroupActivityStatusV2
16
+ from fractal_server.app.schemas.v2.manifest import ManifestV2
17
+ from fractal_server.config import get_settings
18
+ from fractal_server.logger import reset_logger_handlers
19
+ from fractal_server.logger import set_logger
20
+ from fractal_server.ssh._fabric import SingleUseFractalSSH
21
+ from fractal_server.ssh._fabric import SSHConfig
22
+ from fractal_server.syringe import Inject
23
+ from fractal_server.tasks.v2.ssh._utils import _customize_and_run_template
24
+ from fractal_server.tasks.v2.utils_background import add_commit_refresh
25
+ from fractal_server.tasks.v2.utils_background import get_current_log
26
+ from fractal_server.tasks.v2.utils_templates import SCRIPTS_SUBFOLDER
27
+ from fractal_server.utils import get_timestamp
28
+
29
+
30
+ def collect_ssh_pixi(
31
+ *,
32
+ task_group_id: int,
33
+ task_group_activity_id: int,
34
+ ssh_config: SSHConfig,
35
+ tasks_base_dir: str,
36
+ tar_gz_file: FractalUploadedFile,
37
+ ) -> None:
38
+ """
39
+ Collect a task package over SSH
40
+
41
+ This function runs as a background task, therefore exceptions must be
42
+ handled.
43
+
44
+ NOTE: since this function is sync, it runs within a thread - due to
45
+ starlette/fastapi handling of background tasks (see
46
+ https://github.com/encode/starlette/blob/master/starlette/background.py).
47
+
48
+
49
+ Arguments:
50
+ task_group_id:
51
+ task_group_activity_id:
52
+ ssh_config:
53
+ tasks_base_dir:
54
+ Only used as a `safe_root` in `remove_dir`, and typically set to
55
+ `user_settings.ssh_tasks_dir`.
56
+ tar_gz_file:
57
+ """
58
+
59
+ LOGGER_NAME = f"{__name__}.ID{task_group_activity_id}"
60
+
61
+ # Work within a temporary folder, where also logs will be placed
62
+ with TemporaryDirectory() as tmpdir:
63
+ log_file_path = Path(tmpdir) / "log"
64
+ logger = set_logger(
65
+ logger_name=LOGGER_NAME,
66
+ log_file_path=log_file_path,
67
+ )
68
+ logger.info("START")
69
+ with next(get_sync_db()) as db:
70
+ db_objects_ok, task_group, activity = get_activity_and_task_group(
71
+ task_group_activity_id=task_group_activity_id,
72
+ task_group_id=task_group_id,
73
+ db=db,
74
+ logger_name=LOGGER_NAME,
75
+ )
76
+ if not db_objects_ok:
77
+ return
78
+
79
+ with SingleUseFractalSSH(
80
+ ssh_config=ssh_config,
81
+ logger_name=LOGGER_NAME,
82
+ ) as fractal_ssh:
83
+
84
+ try:
85
+ # Check SSH connection
86
+ ssh_ok = check_ssh_or_fail_and_cleanup(
87
+ fractal_ssh=fractal_ssh,
88
+ task_group=task_group,
89
+ task_group_activity=activity,
90
+ logger_name=LOGGER_NAME,
91
+ log_file_path=log_file_path,
92
+ db=db,
93
+ )
94
+ if not ssh_ok:
95
+ return
96
+
97
+ # Check that the (remote) task_group path does not exist
98
+ if fractal_ssh.remote_exists(task_group.path):
99
+ error_msg = f"{task_group.path} already exists."
100
+ logger.error(error_msg)
101
+ fail_and_cleanup(
102
+ task_group=task_group,
103
+ task_group_activity=activity,
104
+ logger_name=LOGGER_NAME,
105
+ log_file_path=log_file_path,
106
+ exception=FileExistsError(error_msg),
107
+ db=db,
108
+ )
109
+ return
110
+
111
+ # Create remote `task_group.path` and `script_dir_remote`
112
+ # folders
113
+ script_dir_remote = Path(
114
+ task_group.path, SCRIPTS_SUBFOLDER
115
+ ).as_posix()
116
+ fractal_ssh.mkdir(folder=task_group.path, parents=True)
117
+ fractal_ssh.mkdir(folder=script_dir_remote, parents=True)
118
+
119
+ # Write tar.gz file locally and send it to remote path,
120
+ # and set task_group.archive_path
121
+ tar_gz_filename = tar_gz_file.filename
122
+ archive_path = (
123
+ Path(task_group.path) / tar_gz_filename
124
+ ).as_posix()
125
+ tmp_archive_path = Path(tmpdir, tar_gz_filename).as_posix()
126
+ logger.info(f"Write tar.gz file into {tmp_archive_path}")
127
+ with open(tmp_archive_path, "wb") as f:
128
+ f.write(tar_gz_file.contents)
129
+ fractal_ssh.send_file(
130
+ local=tmp_archive_path,
131
+ remote=archive_path,
132
+ )
133
+ task_group.archive_path = archive_path
134
+ task_group = add_commit_refresh(obj=task_group, db=db)
135
+
136
+ settings = Inject(get_settings)
137
+ replacements = {
138
+ (
139
+ "__PIXI_HOME__",
140
+ settings.pixi.versions[task_group.pixi_version],
141
+ ),
142
+ ("__PACKAGE_DIR__", task_group.path),
143
+ ("__TAR_GZ_PATH__", task_group.archive_path),
144
+ (
145
+ "__IMPORT_PACKAGE_NAME__",
146
+ task_group.pkg_name.replace("-", "_"),
147
+ ),
148
+ ("__SOURCE_DIR_NAME__", SOURCE_DIR_NAME),
149
+ ("__FROZEN_OPTION__", ""),
150
+ (
151
+ "__TOKIO_WORKER_THREADS__",
152
+ str(settings.pixi.TOKIO_WORKER_THREADS),
153
+ ),
154
+ (
155
+ "__PIXI_CONCURRENT_SOLVES__",
156
+ str(settings.pixi.PIXI_CONCURRENT_SOLVES),
157
+ ),
158
+ (
159
+ "__PIXI_CONCURRENT_DOWNLOADS__",
160
+ str(settings.pixi.PIXI_CONCURRENT_DOWNLOADS),
161
+ ),
162
+ }
163
+
164
+ logger.info("installing - START")
165
+
166
+ # Set status to ONGOING and refresh logs
167
+ activity.status = TaskGroupActivityStatusV2.ONGOING
168
+ activity.log = get_current_log(log_file_path)
169
+ activity = add_commit_refresh(obj=activity, db=db)
170
+
171
+ common_args = dict(
172
+ script_dir_local=(
173
+ Path(tmpdir, SCRIPTS_SUBFOLDER)
174
+ ).as_posix(),
175
+ script_dir_remote=script_dir_remote,
176
+ prefix=(
177
+ f"{int(time.time())}_"
178
+ f"{TaskGroupActivityActionV2.COLLECT}"
179
+ ),
180
+ logger_name=LOGGER_NAME,
181
+ fractal_ssh=fractal_ssh,
182
+ )
183
+
184
+ # Run the three pixi-related scripts
185
+ stdout = _customize_and_run_template(
186
+ template_filename="pixi_1_extract.sh",
187
+ replacements=replacements,
188
+ **common_args,
189
+ )
190
+ logger.debug(f"STDOUT: {stdout}")
191
+ activity.log = get_current_log(log_file_path)
192
+ activity = add_commit_refresh(obj=activity, db=db)
193
+
194
+ stdout = _customize_and_run_template(
195
+ template_filename="pixi_2_install.sh",
196
+ replacements=replacements,
197
+ **common_args,
198
+ )
199
+ logger.debug(f"STDOUT: {stdout}")
200
+ activity.log = get_current_log(log_file_path)
201
+ activity = add_commit_refresh(obj=activity, db=db)
202
+
203
+ stdout = _customize_and_run_template(
204
+ template_filename="pixi_3_post_install.sh",
205
+ replacements=replacements,
206
+ **common_args,
207
+ )
208
+ logger.debug(f"STDOUT: {stdout}")
209
+ activity.log = get_current_log(log_file_path)
210
+ activity = add_commit_refresh(obj=activity, db=db)
211
+
212
+ # Parse stdout
213
+ parsed_output = parse_collect_stdout(stdout)
214
+ package_root_remote = parsed_output["package_root"]
215
+ venv_size = parsed_output["venv_size"]
216
+ venv_file_number = parsed_output["venv_file_number"]
217
+ project_python_wrapper = parsed_output[
218
+ "project_python_wrapper"
219
+ ]
220
+
221
+ source_dir = Path(
222
+ task_group.path, SOURCE_DIR_NAME
223
+ ).as_posix()
224
+ fractal_ssh.run_command(cmd=f"chmod 755 {source_dir} -R")
225
+
226
+ # Read and validate remote manifest file
227
+ manifest_path_remote = (
228
+ f"{package_root_remote}/__FRACTAL_MANIFEST__.json"
229
+ )
230
+ pkg_manifest_dict = fractal_ssh.read_remote_json_file(
231
+ manifest_path_remote
232
+ )
233
+ logger.info(f"Loaded {manifest_path_remote=}")
234
+ pkg_manifest = ManifestV2(**pkg_manifest_dict)
235
+ logger.info("Manifest is a valid ManifestV2")
236
+
237
+ logger.info("_prepare_tasks_metadata - start")
238
+ task_list = prepare_tasks_metadata(
239
+ package_manifest=pkg_manifest,
240
+ package_version=task_group.version,
241
+ package_root=Path(package_root_remote),
242
+ project_python_wrapper=Path(project_python_wrapper),
243
+ )
244
+ logger.info("_prepare_tasks_metadata - end")
245
+
246
+ logger.info(
247
+ "create_db_tasks_and_update_task_group - " "start"
248
+ )
249
+ create_db_tasks_and_update_task_group_sync(
250
+ task_list=task_list,
251
+ task_group_id=task_group.id,
252
+ db=db,
253
+ )
254
+ logger.info("create_db_tasks_and_update_task_group - end")
255
+
256
+ # NOTE: see issue 2626 about whether to keep `pixi.lock`
257
+ # files in the database
258
+ remote_pixi_lock_file = Path(
259
+ task_group.path,
260
+ SOURCE_DIR_NAME,
261
+ "pixi.lock",
262
+ ).as_posix()
263
+ pixi_lock_contents = fractal_ssh.read_remote_text_file(
264
+ remote_pixi_lock_file
265
+ )
266
+
267
+ # Update task_group data
268
+ logger.info(
269
+ "Add env_info, venv_size and venv_file_number "
270
+ "to TaskGroupV2 - start"
271
+ )
272
+ task_group.env_info = pixi_lock_contents
273
+ task_group.venv_size_in_kB = int(venv_size)
274
+ task_group.venv_file_number = int(venv_file_number)
275
+ task_group = add_commit_refresh(obj=task_group, db=db)
276
+ logger.info(
277
+ "Add env_info, venv_size and venv_file_number "
278
+ "to TaskGroupV2 - end"
279
+ )
280
+
281
+ # Finalize (write metadata to DB)
282
+ logger.info("finalising - START")
283
+ activity.status = TaskGroupActivityStatusV2.OK
284
+ activity.timestamp_ended = get_timestamp()
285
+ activity = add_commit_refresh(obj=activity, db=db)
286
+ logger.info("finalising - END")
287
+ logger.info("END")
288
+ reset_logger_handlers(logger)
289
+
290
+ except Exception as collection_e:
291
+ # Delete corrupted package dir
292
+ try:
293
+ logger.info(
294
+ f"Now delete remote folder {task_group.path}"
295
+ )
296
+ fractal_ssh.remove_folder(
297
+ folder=task_group.path,
298
+ safe_root=tasks_base_dir,
299
+ )
300
+ logger.info(
301
+ f"Deleted remoted folder {task_group.path}"
302
+ )
303
+ except Exception as e_rm:
304
+ logger.error(
305
+ "Removing folder failed. "
306
+ f"Original error: {str(e_rm)}"
307
+ )
308
+ fail_and_cleanup(
309
+ task_group=task_group,
310
+ task_group_activity=activity,
311
+ log_file_path=log_file_path,
312
+ logger_name=LOGGER_NAME,
313
+ exception=collection_e,
314
+ db=db,
315
+ )
@@ -1,16 +1,15 @@
1
- import logging
2
1
  import time
3
2
  from pathlib import Path
4
3
  from tempfile import TemporaryDirectory
5
4
 
6
5
  from ..utils_background import add_commit_refresh
7
6
  from ..utils_background import fail_and_cleanup
7
+ from ..utils_background import get_activity_and_task_group
8
8
  from ..utils_templates import get_collection_replacements
9
9
  from ._utils import _copy_wheel_file_ssh
10
10
  from ._utils import _customize_and_run_template
11
+ from ._utils import check_ssh_or_fail_and_cleanup
11
12
  from fractal_server.app.db import get_sync_db
12
- from fractal_server.app.models.v2 import TaskGroupActivityV2
13
- from fractal_server.app.models.v2 import TaskGroupV2
14
13
  from fractal_server.app.schemas.v2 import TaskGroupActivityActionV2
15
14
  from fractal_server.app.schemas.v2 import TaskGroupV2OriginEnum
16
15
  from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
@@ -55,46 +54,33 @@ def deactivate_ssh(
55
54
  logger_name=LOGGER_NAME,
56
55
  log_file_path=log_file_path,
57
56
  )
58
- with SingleUseFractalSSH(
59
- ssh_config=ssh_config,
60
- logger_name=LOGGER_NAME,
61
- ) as fractal_ssh:
62
-
63
- with next(get_sync_db()) as db:
64
-
65
- # Get main objects from db
66
- activity = db.get(TaskGroupActivityV2, task_group_activity_id)
67
- task_group = db.get(TaskGroupV2, task_group_id)
68
- if activity is None or task_group is None:
69
- # Use `logging` directly
70
- logging.error(
71
- "Cannot find database rows with "
72
- f"{task_group_id=} and {task_group_activity_id=}:\n"
73
- f"{task_group=}\n{activity=}. Exit."
74
- )
75
- return
76
-
77
- # Log some info
78
- logger.debug("START")
79
- for key, value in task_group.model_dump().items():
80
- logger.debug(f"task_group.{key}: {value}")
81
-
82
- # Check that SSH connection works
57
+ logger.debug("START")
58
+ with next(get_sync_db()) as db:
59
+ db_objects_ok, task_group, activity = get_activity_and_task_group(
60
+ task_group_activity_id=task_group_activity_id,
61
+ task_group_id=task_group_id,
62
+ db=db,
63
+ logger_name=LOGGER_NAME,
64
+ )
65
+ if not db_objects_ok:
66
+ return
67
+
68
+ with SingleUseFractalSSH(
69
+ ssh_config=ssh_config,
70
+ logger_name=LOGGER_NAME,
71
+ ) as fractal_ssh:
83
72
  try:
84
- fractal_ssh.check_connection()
85
- except Exception as e:
86
- logger.error("Cannot establish SSH connection.")
87
- fail_and_cleanup(
73
+ # Check SSH connection
74
+ ssh_ok = check_ssh_or_fail_and_cleanup(
75
+ fractal_ssh=fractal_ssh,
88
76
  task_group=task_group,
89
77
  task_group_activity=activity,
90
78
  logger_name=LOGGER_NAME,
91
79
  log_file_path=log_file_path,
92
- exception=e,
93
80
  db=db,
94
81
  )
95
- return
96
-
97
- try:
82
+ if not ssh_ok:
83
+ return
98
84
 
99
85
  # Check that the (local) task_group venv_path does exist
100
86
  if not fractal_ssh.remote_exists(task_group.venv_path):
@@ -113,10 +99,10 @@ def deactivate_ssh(
113
99
  activity.status = TaskGroupActivityStatusV2.ONGOING
114
100
  activity = add_commit_refresh(obj=activity, db=db)
115
101
 
116
- if task_group.pip_freeze is None:
102
+ if task_group.env_info is None:
117
103
  logger.warning(
118
104
  "Recreate pip-freeze information, since "
119
- f"{task_group.pip_freeze=}. NOTE: this should "
105
+ f"{task_group.env_info=}. NOTE: this should "
120
106
  "only happen for task groups created before 2.9.0."
121
107
  )
122
108
 
@@ -161,7 +147,7 @@ def deactivate_ssh(
161
147
  )
162
148
  activity.log = get_current_log(log_file_path)
163
149
  activity = add_commit_refresh(obj=activity, db=db)
164
- task_group.pip_freeze = pip_freeze_stdout
150
+ task_group.env_info = pip_freeze_stdout
165
151
  task_group = add_commit_refresh(obj=task_group, db=db)
166
152
  logger.info(
167
153
  "Add pip freeze stdout to TaskGroupV2 - end"
@@ -174,18 +160,19 @@ def deactivate_ssh(
174
160
  f"Handle specific cases for {task_group.origin=}."
175
161
  )
176
162
 
177
- # Blocking situation: `wheel_path` is not set or points
178
- # to a missing path
163
+ # Blocking situation: `archive_path` is not set or
164
+ # points to a missing path
179
165
  if (
180
- task_group.wheel_path is None
166
+ task_group.archive_path is None
181
167
  or not fractal_ssh.remote_exists(
182
- task_group.wheel_path
168
+ task_group.archive_path
183
169
  )
184
170
  ):
185
171
  error_msg = (
186
172
  "Invalid wheel path for task group with "
187
- f"{task_group_id=}. {task_group.wheel_path=} "
188
- "is unset or does not exist."
173
+ f"{task_group_id=}. "
174
+ f"{task_group.archive_path=} is unset or "
175
+ "does not exist."
189
176
  )
190
177
  logger.error(error_msg)
191
178
  fail_and_cleanup(
@@ -198,58 +185,58 @@ def deactivate_ssh(
198
185
  )
199
186
  return
200
187
 
201
- # Recoverable situation: `wheel_path` was not yet
188
+ # Recoverable situation: `archive_path` was not yet
202
189
  # copied over to the correct server-side folder
203
- wheel_path_parent_dir = Path(
204
- task_group.wheel_path
190
+ archive_path_parent_dir = Path(
191
+ task_group.archive_path
205
192
  ).parent
206
- if wheel_path_parent_dir != Path(task_group.path):
193
+ if archive_path_parent_dir != Path(task_group.path):
207
194
  logger.warning(
208
- f"{wheel_path_parent_dir.as_posix()} differs "
209
- f"from {task_group.path}. NOTE: this should "
210
- "only happen for task groups created before "
211
- "2.9.0."
195
+ f"{archive_path_parent_dir.as_posix()} "
196
+ f"differs from {task_group.path}. "
197
+ "NOTE: this should only happen for task "
198
+ "groups created before 2.9.0."
212
199
  )
213
200
 
214
201
  if (
215
- task_group.wheel_path
216
- not in task_group.pip_freeze
202
+ task_group.archive_path
203
+ not in task_group.env_info
217
204
  ):
218
205
  raise ValueError(
219
- f"Cannot find {task_group.wheel_path=} in "
220
- "pip-freeze data. Exit."
206
+ f"Cannot find {task_group.archive_path=} "
207
+ "in pip-freeze data. Exit."
221
208
  )
222
209
 
223
210
  logger.info(
224
211
  f"Now copy wheel file into {task_group.path}."
225
212
  )
226
- new_wheel_path = _copy_wheel_file_ssh(
213
+ new_archive_path = _copy_wheel_file_ssh(
227
214
  task_group=task_group,
228
215
  fractal_ssh=fractal_ssh,
229
216
  logger_name=LOGGER_NAME,
230
217
  )
231
218
  logger.info(
232
- f"Copied wheel file to {new_wheel_path}."
219
+ f"Copied wheel file to {new_archive_path}."
233
220
  )
234
221
 
235
- task_group.wheel_path = new_wheel_path
236
- new_pip_freeze = task_group.pip_freeze.replace(
237
- task_group.wheel_path,
238
- new_wheel_path,
222
+ task_group.archive_path = new_archive_path
223
+ new_pip_freeze = task_group.env_info.replace(
224
+ task_group.archive_path,
225
+ new_archive_path,
239
226
  )
240
- task_group.pip_freeze = new_pip_freeze
227
+ task_group.env_info = new_pip_freeze
241
228
  task_group = add_commit_refresh(
242
229
  obj=task_group, db=db
243
230
  )
244
231
  logger.info(
245
- "Updated `wheel_path` and `pip_freeze` "
232
+ "Updated `archive_path` and `env_info` "
246
233
  "task-group attributes."
247
234
  )
248
235
 
249
- # Fail if `pip_freeze` includes "github", see
236
+ # Fail if `env_info` includes "github", see
250
237
  # https://github.com/fractal-analytics-platform/fractal-server/issues/2142
251
238
  for forbidden_string in FORBIDDEN_DEPENDENCY_STRINGS:
252
- if forbidden_string in task_group.pip_freeze:
239
+ if forbidden_string in task_group.env_info:
253
240
  raise ValueError(
254
241
  "Deactivation and reactivation of task "
255
242
  f"packages with direct {forbidden_string} "
@@ -0,0 +1,122 @@
1
+ from pathlib import Path
2
+ from tempfile import TemporaryDirectory
3
+
4
+ from ..utils_background import add_commit_refresh
5
+ from ..utils_background import fail_and_cleanup
6
+ from ..utils_background import get_activity_and_task_group
7
+ from ..utils_pixi import SOURCE_DIR_NAME
8
+ from ._utils import check_ssh_or_fail_and_cleanup
9
+ from fractal_server.app.db import get_sync_db
10
+ from fractal_server.app.schemas.v2.task_group import TaskGroupActivityStatusV2
11
+ from fractal_server.logger import reset_logger_handlers
12
+ from fractal_server.logger import set_logger
13
+ from fractal_server.ssh._fabric import SingleUseFractalSSH
14
+ from fractal_server.ssh._fabric import SSHConfig
15
+ from fractal_server.tasks.utils import get_log_path
16
+ from fractal_server.tasks.v2.utils_background import get_current_log
17
+ from fractal_server.utils import get_timestamp
18
+
19
+
20
+ def deactivate_ssh_pixi(
21
+ *,
22
+ task_group_activity_id: int,
23
+ task_group_id: int,
24
+ ssh_config: SSHConfig,
25
+ tasks_base_dir: str,
26
+ ) -> None:
27
+ """
28
+ Deactivate a pixi task group venv.
29
+
30
+ This function is run as a background task, therefore exceptions must be
31
+ handled.
32
+
33
+ Arguments:
34
+ task_group_id:
35
+ task_group_activity_id:
36
+ ssh_config:
37
+ tasks_base_dir:
38
+ Only used as a `safe_root` in `remove_dir`, and typically set to
39
+ `user_settings.ssh_tasks_dir`.
40
+ """
41
+
42
+ LOGGER_NAME = f"{__name__}.ID{task_group_activity_id}"
43
+
44
+ with TemporaryDirectory() as tmpdir:
45
+ log_file_path = get_log_path(Path(tmpdir))
46
+ logger = set_logger(
47
+ logger_name=LOGGER_NAME,
48
+ log_file_path=log_file_path,
49
+ )
50
+ logger.debug("START")
51
+ with next(get_sync_db()) as db:
52
+ db_objects_ok, task_group, activity = get_activity_and_task_group(
53
+ task_group_activity_id=task_group_activity_id,
54
+ task_group_id=task_group_id,
55
+ db=db,
56
+ logger_name=LOGGER_NAME,
57
+ )
58
+ if not db_objects_ok:
59
+ return
60
+
61
+ with SingleUseFractalSSH(
62
+ ssh_config=ssh_config,
63
+ logger_name=LOGGER_NAME,
64
+ ) as fractal_ssh:
65
+ try:
66
+ # Check SSH connection
67
+ ssh_ok = check_ssh_or_fail_and_cleanup(
68
+ fractal_ssh=fractal_ssh,
69
+ task_group=task_group,
70
+ task_group_activity=activity,
71
+ logger_name=LOGGER_NAME,
72
+ log_file_path=log_file_path,
73
+ db=db,
74
+ )
75
+ if not ssh_ok:
76
+ return
77
+
78
+ # Check that the (remote) task_group venv_path does exist
79
+ source_dir = Path(
80
+ task_group.path, SOURCE_DIR_NAME
81
+ ).as_posix()
82
+ if not fractal_ssh.remote_exists(source_dir):
83
+ error_msg = f"{source_dir} does not exist."
84
+ logger.error(error_msg)
85
+ fail_and_cleanup(
86
+ task_group=task_group,
87
+ task_group_activity=activity,
88
+ logger_name=LOGGER_NAME,
89
+ log_file_path=log_file_path,
90
+ exception=FileNotFoundError(error_msg),
91
+ db=db,
92
+ )
93
+ return
94
+
95
+ # Actually mark the task group as non-active
96
+ logger.info("Now setting `active=False`.")
97
+ task_group.active = False
98
+ task_group = add_commit_refresh(obj=task_group, db=db)
99
+
100
+ # Proceed with deactivation
101
+ logger.info(f"Now removing {source_dir}.")
102
+ fractal_ssh.remove_folder(
103
+ folder=source_dir,
104
+ safe_root=tasks_base_dir,
105
+ )
106
+ logger.info(f"All good, {source_dir} removed.")
107
+ activity.status = TaskGroupActivityStatusV2.OK
108
+ activity.log = get_current_log(log_file_path)
109
+ activity.timestamp_ended = get_timestamp()
110
+ activity = add_commit_refresh(obj=activity, db=db)
111
+
112
+ reset_logger_handlers(logger)
113
+
114
+ except Exception as e:
115
+ fail_and_cleanup(
116
+ task_group=task_group,
117
+ task_group_activity=activity,
118
+ logger_name=LOGGER_NAME,
119
+ log_file_path=log_file_path,
120
+ exception=e,
121
+ db=db,
122
+ )