simstack 2.0.1__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.
- simstack/__init__.py +0 -0
- simstack/_version.py +24 -0
- simstack/core/__init__.py +0 -0
- simstack/core/artifacts.py +406 -0
- simstack/core/asnyc_helper.py +14 -0
- simstack/core/config_file.py +60 -0
- simstack/core/context.py +283 -0
- simstack/core/definitions.py +22 -0
- simstack/core/engine.py +115 -0
- simstack/core/find_simstack_modules.py +41 -0
- simstack/core/hash.py +179 -0
- simstack/core/node.py +1137 -0
- simstack/core/node_claim.py +31 -0
- simstack/core/node_runner.py +362 -0
- simstack/core/process_results.py +102 -0
- simstack/core/recompute_artifacts.py +117 -0
- simstack/core/reset_db.py +12 -0
- simstack/core/resource_assignment.py +222 -0
- simstack/core/resources.py +90 -0
- simstack/core/route_table.py +24 -0
- simstack/core/run_docker.py +109 -0
- simstack/core/run_node.py +71 -0
- simstack/core/runner.py +169 -0
- simstack/core/services/__init__.py +11 -0
- simstack/core/services/base_service.py +191 -0
- simstack/core/services/file_transfer_service.py +117 -0
- simstack/core/services/git_restart_service.py +12 -0
- simstack/core/services/git_uv_update_service.py +105 -0
- simstack/core/services/node_execution_service.py +182 -0
- simstack/core/services/resource_branch_monitor_service.py +71 -0
- simstack/core/services/runner_cleanup_service.py +149 -0
- simstack/core/services/runner_manager.py +163 -0
- simstack/core/services/runner_status_service.py +10 -0
- simstack/core/services/slurm_status_service.py +67 -0
- simstack/core/services/stop_check_service.py +27 -0
- simstack/core/services/timeout_restart_service.py +26 -0
- simstack/core/simstack_result.py +39 -0
- simstack/core/submit_node.py +217 -0
- simstack/core/task_id.py +22 -0
- simstack/methods/__init__.py +0 -0
- simstack/methods/archive_files.py +292 -0
- simstack/methods/fire_and_forget_runner.py +113 -0
- simstack/methods/generate_test.py +125 -0
- simstack/methods/get_file.py +26 -0
- simstack/methods/mass_runner.py +154 -0
- simstack/methods/multiple_executor.py +307 -0
- simstack/methods/switch_git.py +16 -0
- simstack/methods/upload_helpers.py +81 -0
- simstack/models/__init__.py +74 -0
- simstack/models/array_list.py +21 -0
- simstack/models/array_storage.py +83 -0
- simstack/models/artifact_models.py +127 -0
- simstack/models/base_lists.py +51 -0
- simstack/models/base_types.py +209 -0
- simstack/models/charts_artifact.py +690 -0
- simstack/models/dataset.py +451 -0
- simstack/models/dataset_metadata.py +390 -0
- simstack/models/datasettuple.py +587 -0
- simstack/models/file_instance.py +150 -0
- simstack/models/file_list.py +45 -0
- simstack/models/files.py +424 -0
- simstack/models/fire_and_forget_result.py +19 -0
- simstack/models/images2d.py +113 -0
- simstack/models/log_entry_model.py +37 -0
- simstack/models/models.py +52 -0
- simstack/models/named_data_reference.py +36 -0
- simstack/models/node_registry.py +96 -0
- simstack/models/pandas_model.py +239 -0
- simstack/models/parameters.py +402 -0
- simstack/models/pickle_models.py +300 -0
- simstack/models/project.py +13 -0
- simstack/models/resource_assignment.py +170 -0
- simstack/models/resource_definition.py +125 -0
- simstack/models/runner_model.py +39 -0
- simstack/models/simple_table.py +79 -0
- simstack/models/simstack_model.py +97 -0
- simstack/models/slurm_info.py +17 -0
- simstack/models/table_artifact.py +87 -0
- simstack/models/tag.py +12 -0
- simstack/models/unused_test_file.py +5 -0
- simstack/simstack_marker.com +1 -0
- simstack/static/docs/.buildinfo +4 -0
- simstack/static/docs/_images/GUI Concept.webp +0 -0
- simstack/static/docs/_modules/index.html +434 -0
- simstack/static/docs/_modules/simstack/core/artifacts.html +743 -0
- simstack/static/docs/_modules/simstack/core/asnyc_helper.html +330 -0
- simstack/static/docs/_modules/simstack/core/config_file.html +379 -0
- simstack/static/docs/_modules/simstack/core/definitions.html +341 -0
- simstack/static/docs/_modules/simstack/core/engine.html +437 -0
- simstack/static/docs/_modules/simstack/core/find_simstack_modules.html +360 -0
- simstack/static/docs/_modules/simstack/core/hash.html +528 -0
- simstack/static/docs/_modules/simstack/core/node.html +1392 -0
- simstack/static/docs/_modules/simstack/core/node_claim.html +347 -0
- simstack/static/docs/_modules/simstack/core/node_runner.html +711 -0
- simstack/static/docs/_modules/simstack/core/process_results.html +418 -0
- simstack/static/docs/_modules/simstack/core/recompute_artifacts.html +433 -0
- simstack/static/docs/_modules/simstack/core/reset_db.html +328 -0
- simstack/static/docs/_modules/simstack/core/resource_assignment.html +553 -0
- simstack/static/docs/_modules/simstack/core/resources.html +424 -0
- simstack/static/docs/_modules/simstack/core/route_table.html +349 -0
- simstack/static/docs/_modules/simstack/core/run_docker.html +425 -0
- simstack/static/docs/_modules/simstack/core/run_node.html +390 -0
- simstack/static/docs/_modules/simstack/core/runner.html +491 -0
- simstack/static/docs/_modules/simstack/core/services/base_service.html +522 -0
- simstack/static/docs/_modules/simstack/core/services/file_transfer_service.html +436 -0
- simstack/static/docs/_modules/simstack/core/services/git_restart_service.html +331 -0
- simstack/static/docs/_modules/simstack/core/services/git_uv_update_service.html +427 -0
- simstack/static/docs/_modules/simstack/core/services/node_execution_service.html +507 -0
- simstack/static/docs/_modules/simstack/core/services/resource_branch_monitor_service.html +390 -0
- simstack/static/docs/_modules/simstack/core/services/runner_cleanup_service.html +468 -0
- simstack/static/docs/_modules/simstack/core/services/runner_manager.html +485 -0
- simstack/static/docs/_modules/simstack/core/services/runner_status_service.html +329 -0
- simstack/static/docs/_modules/simstack/core/services/slurm_status_service.html +386 -0
- simstack/static/docs/_modules/simstack/core/services/stop_check_service.html +346 -0
- simstack/static/docs/_modules/simstack/core/services/timeout_restart_service.html +345 -0
- simstack/static/docs/_modules/simstack/core/simstack_result.html +355 -0
- simstack/static/docs/_modules/simstack/core/submit_node.html +536 -0
- simstack/static/docs/_modules/simstack/core/task_id.html +344 -0
- simstack/static/docs/_modules/simstack/methods/archive_files.html +598 -0
- simstack/static/docs/_modules/simstack/methods/fire_and_forget_runner.html +432 -0
- simstack/static/docs/_modules/simstack/methods/generate_test.html +443 -0
- simstack/static/docs/_modules/simstack/methods/get_file.html +342 -0
- simstack/static/docs/_modules/simstack/methods/mass_runner.html +476 -0
- simstack/static/docs/_modules/simstack/methods/multiple_executor.html +674 -0
- simstack/static/docs/_modules/simstack/methods/switch_git.html +332 -0
- simstack/static/docs/_modules/simstack/methods/upload_helpers.html +403 -0
- simstack/static/docs/_modules/simstack/models/array_list.html +337 -0
- simstack/static/docs/_modules/simstack/models/array_storage.html +417 -0
- simstack/static/docs/_modules/simstack/models/base_lists.html +374 -0
- simstack/static/docs/_modules/simstack/models/base_types.html +588 -0
- simstack/static/docs/_modules/simstack/models/charts_artifact.html +1035 -0
- simstack/static/docs/_modules/simstack/models/dataset.html +866 -0
- simstack/static/docs/_modules/simstack/models/dataset_metadata.html +711 -0
- simstack/static/docs/_modules/simstack/models/datasettuple.html +1005 -0
- simstack/static/docs/_modules/simstack/models/file_instance.html +472 -0
- simstack/static/docs/_modules/simstack/models/file_list.html +367 -0
- simstack/static/docs/_modules/simstack/models/files.html +773 -0
- simstack/static/docs/_modules/simstack/models/fire_and_forget_result.html +335 -0
- simstack/static/docs/_modules/simstack/models/images2d.html +447 -0
- simstack/static/docs/_modules/simstack/models/log_entry_model.html +356 -0
- simstack/static/docs/_modules/simstack/models/models.html +374 -0
- simstack/static/docs/_modules/simstack/models/named_data_reference.html +355 -0
- simstack/static/docs/_modules/simstack/models/node_registry.html +415 -0
- simstack/static/docs/_modules/simstack/models/pandas_model.html +570 -0
- simstack/static/docs/_modules/simstack/models/parameters.html +739 -0
- simstack/static/docs/_modules/simstack/models/pickle_models.html +631 -0
- simstack/static/docs/_modules/simstack/models/project.html +329 -0
- simstack/static/docs/_modules/simstack/models/resource_assignment.html +501 -0
- simstack/static/docs/_modules/simstack/models/resource_definition.html +477 -0
- simstack/static/docs/_modules/simstack/models/runner_model.html +361 -0
- simstack/static/docs/_modules/simstack/models/simple_table.html +413 -0
- simstack/static/docs/_modules/simstack/models/simstack_model.html +416 -0
- simstack/static/docs/_modules/simstack/models/slurm_info.html +333 -0
- simstack/static/docs/_modules/simstack/models/table_artifact.html +406 -0
- simstack/static/docs/_modules/simstack/models/tag.html +328 -0
- simstack/static/docs/_modules/simstack/tables/model_table.html +519 -0
- simstack/static/docs/_modules/simstack/tables/node_children.html +538 -0
- simstack/static/docs/_modules/simstack/tables/node_table.html +854 -0
- simstack/static/docs/_modules/simstack/tables/table_builder.html +642 -0
- simstack/static/docs/_modules/simstack/util/async_zip_utils.html +337 -0
- simstack/static/docs/_modules/simstack/util/b64mixin.html +339 -0
- simstack/static/docs/_modules/simstack/util/cleaned_json_schema.html +336 -0
- simstack/static/docs/_modules/simstack/util/config_reader.html +498 -0
- simstack/static/docs/_modules/simstack/util/custom_model_dump.html +391 -0
- simstack/static/docs/_modules/simstack/util/database_information.html +488 -0
- simstack/static/docs/_modules/simstack/util/db.html +1392 -0
- simstack/static/docs/_modules/simstack/util/db_logger.html +448 -0
- simstack/static/docs/_modules/simstack/util/default_from_dict.html +376 -0
- simstack/static/docs/_modules/simstack/util/directory_iterator.html +553 -0
- simstack/static/docs/_modules/simstack/util/docstring_parser.html +526 -0
- simstack/static/docs/_modules/simstack/util/file_hashing.html +394 -0
- simstack/static/docs/_modules/simstack/util/file_transfer_client.html +791 -0
- simstack/static/docs/_modules/simstack/util/files.html +733 -0
- simstack/static/docs/_modules/simstack/util/find_python_files.html +349 -0
- simstack/static/docs/_modules/simstack/util/generate_ui_schema.html +396 -0
- simstack/static/docs/_modules/simstack/util/generic_list_mixin.html +487 -0
- simstack/static/docs/_modules/simstack/util/get_module_path.html +336 -0
- simstack/static/docs/_modules/simstack/util/git_pull.html +381 -0
- simstack/static/docs/_modules/simstack/util/git_repository_status.html +411 -0
- simstack/static/docs/_modules/simstack/util/import_module.html +377 -0
- simstack/static/docs/_modules/simstack/util/importer.html +637 -0
- simstack/static/docs/_modules/simstack/util/init_data_source.html +357 -0
- simstack/static/docs/_modules/simstack/util/make_graph.html +423 -0
- simstack/static/docs/_modules/simstack/util/make_table.html +1171 -0
- simstack/static/docs/_modules/simstack/util/mappings.html +378 -0
- simstack/static/docs/_modules/simstack/util/minimal_route_finder.html +388 -0
- simstack/static/docs/_modules/simstack/util/mock_db.html +472 -0
- simstack/static/docs/_modules/simstack/util/module_path_checker.html +354 -0
- simstack/static/docs/_modules/simstack/util/object_list_mixin.html +649 -0
- simstack/static/docs/_modules/simstack/util/path_manager.html +566 -0
- simstack/static/docs/_modules/simstack/util/project_root_finder.html +357 -0
- simstack/static/docs/_modules/simstack/util/queue_watcher.html +593 -0
- simstack/static/docs/_modules/simstack/util/resource_config.html +532 -0
- simstack/static/docs/_modules/simstack/util/route_finder.html +390 -0
- simstack/static/docs/_modules/simstack/util/runner_util.html +487 -0
- simstack/static/docs/_modules/simstack/util/runner_utils.html +468 -0
- simstack/static/docs/_modules/simstack/util/safe_code_executor.html +510 -0
- simstack/static/docs/_modules/simstack/util/sample_config.html +414 -0
- simstack/static/docs/_modules/simstack/util/sensitive_content_filter.html +364 -0
- simstack/static/docs/_modules/simstack/util/setup_logging.html +365 -0
- simstack/static/docs/_modules/simstack/util/submit_to_watchdog.html +425 -0
- simstack/static/docs/_modules/simstack/util/toml_reader.html +482 -0
- simstack/static/docs/_modules/simstack/util/transform_file_name.html +357 -0
- simstack/static/docs/_modules/simstack/util/ui_tools.html +541 -0
- simstack/static/docs/_sources/architecture.rst.txt +25 -0
- simstack/static/docs/_sources/array_lists.rst.txt +60 -0
- simstack/static/docs/_sources/ci_pipeline.rst.txt +34 -0
- simstack/static/docs/_sources/data.rst.txt +175 -0
- simstack/static/docs/_sources/database_migrations.rst.txt +310 -0
- simstack/static/docs/_sources/datasets.rst.txt +93 -0
- simstack/static/docs/_sources/developer_guide.rst.txt +25 -0
- simstack/static/docs/_sources/docker_nodes.rst.txt +149 -0
- simstack/static/docs/_sources/files.rst.txt +159 -0
- simstack/static/docs/_sources/fire_and_forget_runner.rst.txt +67 -0
- simstack/static/docs/_sources/index.rst.txt +25 -0
- simstack/static/docs/_sources/installation.rst.txt +213 -0
- simstack/static/docs/_sources/lists.rst.txt +111 -0
- simstack/static/docs/_sources/mass_runner.rst.txt +91 -0
- simstack/static/docs/_sources/misc.rst.txt +106 -0
- simstack/static/docs/_sources/overview.rst.txt +91 -0
- simstack/static/docs/_sources/recovery.rst.txt +38 -0
- simstack/static/docs/_sources/resource_config.rst.txt +141 -0
- simstack/static/docs/_sources/simstack/modules/modules.rst.txt +7 -0
- simstack/static/docs/_sources/simstack/modules/simstack.core.rst.txt +205 -0
- simstack/static/docs/_sources/simstack/modules/simstack.core.services.rst.txt +109 -0
- simstack/static/docs/_sources/simstack/modules/simstack.methods.rst.txt +77 -0
- simstack/static/docs/_sources/simstack/modules/simstack.models.rst.txt +261 -0
- simstack/static/docs/_sources/simstack/modules/simstack.rst.txt +22 -0
- simstack/static/docs/_sources/simstack/modules/simstack.tables.rst.txt +45 -0
- simstack/static/docs/_sources/simstack/modules/simstack.util.rst.txt +381 -0
- simstack/static/docs/_sources/submit.rst.txt +141 -0
- simstack/static/docs/_sources/tables.rst.txt +50 -0
- simstack/static/docs/_sources/testing_complex_nodes.rst.txt +92 -0
- simstack/static/docs/_sources/ui_documentation.rst.txt +272 -0
- simstack/static/docs/_sources/workflows.rst.txt +446 -0
- simstack/static/docs/_static/base-stemmer.js +476 -0
- simstack/static/docs/_static/basic.css +906 -0
- simstack/static/docs/_static/cobalt2-theme.css +1 -0
- simstack/static/docs/_static/debug.css +69 -0
- simstack/static/docs/_static/docs-build-badge.css +51 -0
- simstack/static/docs/_static/docs-build-badge.js +51 -0
- simstack/static/docs/_static/doctools.js +150 -0
- simstack/static/docs/_static/documentation_options.js +13 -0
- simstack/static/docs/_static/english-stemmer.js +1066 -0
- simstack/static/docs/_static/file.png +0 -0
- simstack/static/docs/_static/language_data.js +13 -0
- simstack/static/docs/_static/minus.png +0 -0
- simstack/static/docs/_static/plus.png +0 -0
- simstack/static/docs/_static/pygments.css +250 -0
- simstack/static/docs/_static/scripts/furo-extensions.js +0 -0
- simstack/static/docs/_static/scripts/furo.js +3 -0
- simstack/static/docs/_static/scripts/furo.js.LICENSE.txt +7 -0
- simstack/static/docs/_static/scripts/furo.js.map +1 -0
- simstack/static/docs/_static/searchtools.js +693 -0
- simstack/static/docs/_static/skeleton.css +296 -0
- simstack/static/docs/_static/sphinx_highlight.js +159 -0
- simstack/static/docs/_static/styles/furo-extensions.css +2 -0
- simstack/static/docs/_static/styles/furo-extensions.css.map +1 -0
- simstack/static/docs/_static/styles/furo.css +2 -0
- simstack/static/docs/_static/styles/furo.css.map +1 -0
- simstack/static/docs/architecture.html +340 -0
- simstack/static/docs/array_lists.html +678 -0
- simstack/static/docs/ci_pipeline.html +385 -0
- simstack/static/docs/data.html +545 -0
- simstack/static/docs/database_migrations.html +652 -0
- simstack/static/docs/datasets.html +1268 -0
- simstack/static/docs/developer_guide.html +359 -0
- simstack/static/docs/docker_nodes.html +503 -0
- simstack/static/docs/files.html +718 -0
- simstack/static/docs/fire_and_forget_runner.html +641 -0
- simstack/static/docs/genindex.html +6390 -0
- simstack/static/docs/index.html +516 -0
- simstack/static/docs/installation.html +553 -0
- simstack/static/docs/lists.html +478 -0
- simstack/static/docs/mass_runner.html +481 -0
- simstack/static/docs/misc.html +454 -0
- simstack/static/docs/objects.inv +0 -0
- simstack/static/docs/overview.html +469 -0
- simstack/static/docs/py-modindex.html +1111 -0
- simstack/static/docs/recovery.html +371 -0
- simstack/static/docs/resource_config.html +590 -0
- simstack/static/docs/search.html +327 -0
- simstack/static/docs/searchindex.js +1 -0
- simstack/static/docs/simstack/modules/modules.html +489 -0
- simstack/static/docs/simstack/modules/simstack.core.html +1883 -0
- simstack/static/docs/simstack/modules/simstack.core.services.html +698 -0
- simstack/static/docs/simstack/modules/simstack.html +3060 -0
- simstack/static/docs/simstack/modules/simstack.methods.html +1150 -0
- simstack/static/docs/simstack/modules/simstack.models.html +13113 -0
- simstack/static/docs/simstack/modules/simstack.tables.html +635 -0
- simstack/static/docs/simstack/modules/simstack.util.html +3320 -0
- simstack/static/docs/submit.html +487 -0
- simstack/static/docs/tables.html +539 -0
- simstack/static/docs/testing_complex_nodes.html +458 -0
- simstack/static/docs/ui_documentation.html +788 -0
- simstack/static/docs/workflows.html +766 -0
- simstack/tables/__init__.py +0 -0
- simstack/tables/model_table.py +191 -0
- simstack/tables/node_children.py +219 -0
- simstack/tables/node_table.py +517 -0
- simstack/tables/table_builder.py +314 -0
- simstack/util/__init__.py +0 -0
- simstack/util/async_zip_utils.py +18 -0
- simstack/util/b64mixin.py +23 -0
- simstack/util/cleaned_json_schema.py +20 -0
- simstack/util/config_reader.py +179 -0
- simstack/util/custom_model_dump.py +75 -0
- simstack/util/database_information.py +160 -0
- simstack/util/db.py +1013 -0
- simstack/util/db_logger.py +117 -0
- simstack/util/default_from_dict.py +57 -0
- simstack/util/directory_iterator.py +216 -0
- simstack/util/docstring_parser.py +186 -0
- simstack/util/file_hashing.py +72 -0
- simstack/util/file_transfer_client.py +427 -0
- simstack/util/files.py +396 -0
- simstack/util/find_python_files.py +33 -0
- simstack/util/generate_ui_schema.py +80 -0
- simstack/util/generic_list_mixin.py +120 -0
- simstack/util/geometry_utils.py +118 -0
- simstack/util/get_module_path.py +20 -0
- simstack/util/git_pull.py +65 -0
- simstack/util/git_repository_status.py +95 -0
- simstack/util/import_module.py +63 -0
- simstack/util/importer.py +312 -0
- simstack/util/init_data_source.py +41 -0
- simstack/util/make_graph.py +107 -0
- simstack/util/make_table.py +837 -0
- simstack/util/mappings.py +41 -0
- simstack/util/minimal_route_finder.py +72 -0
- simstack/util/mock_db.py +144 -0
- simstack/util/module_path_checker.py +38 -0
- simstack/util/object_list_mixin.py +282 -0
- simstack/util/path_manager.py +223 -0
- simstack/util/project_root_finder.py +41 -0
- simstack/util/queue_watcher.py +262 -0
- simstack/util/resource_config.py +198 -0
- simstack/util/route_finder.py +74 -0
- simstack/util/runner_util.py +166 -0
- simstack/util/runner_utils.py +143 -0
- simstack/util/safe_code_executor.py +194 -0
- simstack/util/sample_config.py +98 -0
- simstack/util/sensitive_content_filter.py +48 -0
- simstack/util/setup_logging.py +49 -0
- simstack/util/submit_to_watchdog.py +106 -0
- simstack/util/toml_reader.py +142 -0
- simstack/util/transform_file_name.py +41 -0
- simstack/util/ui_tools.py +213 -0
- simstack-2.0.1.dist-info/METADATA +454 -0
- simstack-2.0.1.dist-info/RECORD +354 -0
- simstack-2.0.1.dist-info/WHEEL +5 -0
- simstack-2.0.1.dist-info/entry_points.txt +12 -0
- simstack-2.0.1.dist-info/licenses/LICENSE +21 -0
- simstack-2.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import stat
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from simstack.core.context import context
|
|
8
|
+
from simstack.core.definitions import TaskStatus
|
|
9
|
+
from simstack.models import NodeRegistry
|
|
10
|
+
from simstack.util.submit_to_watchdog import submit_to_watchdog
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("submit_node")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_executable(file_path: str | os.PathLike[str]) -> None:
|
|
17
|
+
# Get current permissions
|
|
18
|
+
current_permissions = os.stat(file_path).st_mode
|
|
19
|
+
|
|
20
|
+
# Add executable bit for user, group and others
|
|
21
|
+
executable_mode = current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
|
22
|
+
|
|
23
|
+
# Apply new permissions
|
|
24
|
+
os.chmod(file_path, executable_mode)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def submit_node(registry_entry: NodeRegistry) -> bool:
|
|
28
|
+
"""Submit a node to the SLURM queue"""
|
|
29
|
+
task_id = registry_entry.id
|
|
30
|
+
try:
|
|
31
|
+
logger.info(f"Submitting task_id: {task_id} to SLURM queue")
|
|
32
|
+
# Implement SLURM submission logic here
|
|
33
|
+
base_path = context.config.project_root
|
|
34
|
+
|
|
35
|
+
python_path = ":".join(str(path) for path in context.config.python_paths)
|
|
36
|
+
work_dir = context.config.workdir / registry_entry.name / str(registry_entry.id)
|
|
37
|
+
job_name = registry_entry.name + "." + str(registry_entry.id)
|
|
38
|
+
|
|
39
|
+
logger.info(f"task_id: {task_id} workdir {work_dir} python path {python_path}")
|
|
40
|
+
# write a slurm script that starts a python script "run_node.py" with the node id as an argument
|
|
41
|
+
|
|
42
|
+
original_slurm_parameters = registry_entry.parameters.slurm_parameters
|
|
43
|
+
if original_slurm_parameters is None:
|
|
44
|
+
logger.error(f"Task task_id: {task_id} has no slurm parameters -- failing")
|
|
45
|
+
registry_entry.status = TaskStatus.FAILED
|
|
46
|
+
await context.db.save(registry_entry)
|
|
47
|
+
return False
|
|
48
|
+
slurm_parameters = original_slurm_parameters.model_copy(deep=True)
|
|
49
|
+
slurm_parameters.output = f"{work_dir}/%j.out"
|
|
50
|
+
slurm_parameters.error = f"{work_dir}/%j.err"
|
|
51
|
+
slurm_parameters.job_name = f"{job_name}"
|
|
52
|
+
slurm_parameters.chdir = str(work_dir)
|
|
53
|
+
|
|
54
|
+
slurm_parameters.startup_commands.append("source ~/.bashrc")
|
|
55
|
+
slurm_parameters.startup_commands.append(f"{context.config.environment_start}")
|
|
56
|
+
slurm_parameters.startup_commands.append(
|
|
57
|
+
f"export PYTHONPATH={python_path}:$PYTHONPATH"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
sync_data_start = """# 1. Define the rsync function
|
|
61
|
+
sync_data() {
|
|
62
|
+
echo "=== Time limit approaching! Starting rsync at $(date) ==="
|
|
63
|
+
|
|
64
|
+
# Run rsync safely
|
|
65
|
+
"""
|
|
66
|
+
sync_data_end = """
|
|
67
|
+
echo "=== Rsync completed at $(date) ==="
|
|
68
|
+
exit 0
|
|
69
|
+
}
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if context.resource_config is not None:
|
|
73
|
+
program_config = context.resource_config.get_program(registry_entry.name)
|
|
74
|
+
else:
|
|
75
|
+
program_config = {}
|
|
76
|
+
|
|
77
|
+
logger.info(f"task_id: {task_id} program_config {program_config}")
|
|
78
|
+
if program_config.get("use_tmp", False):
|
|
79
|
+
tmp_dir = context.resource_config.tmp_dir(registry_entry.id)
|
|
80
|
+
|
|
81
|
+
if not tmp_dir.exists() or not tmp_dir.is_dir():
|
|
82
|
+
logger.error(
|
|
83
|
+
f"Task task_id: {task_id} tmp_dir {tmp_dir} does not exist or is not a directory -- failing")
|
|
84
|
+
registry_entry.status = TaskStatus.FAILED
|
|
85
|
+
await context.db.save(registry_entry)
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
full_sync_data = sync_data_start + f'cp -a {tmp_dir}/. {work_dir}/recovery\n' + sync_data_end
|
|
89
|
+
|
|
90
|
+
logger.info(f"task_id: {task_id} full_sync_data {full_sync_data}")
|
|
91
|
+
slurm_parameters.startup_commands.append(full_sync_data)
|
|
92
|
+
slurm_parameters.startup_commands.append("trap 'sync_data' SIGUSR1")
|
|
93
|
+
|
|
94
|
+
if context.config.docker:
|
|
95
|
+
external_work_dir = (
|
|
96
|
+
context.config.external_workdir
|
|
97
|
+
/ registry_entry.name
|
|
98
|
+
/ str(registry_entry.id)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
watcher_file = (
|
|
102
|
+
context.config.project_root
|
|
103
|
+
/ "src"
|
|
104
|
+
/ "simstack"
|
|
105
|
+
/ "util"
|
|
106
|
+
/ "queue_watcher.py"
|
|
107
|
+
)
|
|
108
|
+
with open(watcher_file, "r") as f:
|
|
109
|
+
watcher_content = f.read()
|
|
110
|
+
slurm_parameters.startup_commands.append(
|
|
111
|
+
f"cat > watcher.py <<'EOF'\n{watcher_content}\nEOF"
|
|
112
|
+
)
|
|
113
|
+
slurm_parameters.startup_commands.append("python watcher.py &")
|
|
114
|
+
slurm_parameters.startup_commands.append("WATCHER_PID=$!")
|
|
115
|
+
|
|
116
|
+
slurm_parameters.startup_commands.append("set -euo pipefail")
|
|
117
|
+
slurm_parameters.startup_commands.append(
|
|
118
|
+
"cleanup() { "
|
|
119
|
+
'if [ -n "${WATCHER_PID:-}" ] && kill -0 "$WATCHER_PID" 2>/dev/null; then '
|
|
120
|
+
' kill "$WATCHER_PID" 2>/dev/null || true; '
|
|
121
|
+
" sleep 2; "
|
|
122
|
+
' kill -9 "$WATCHER_PID" 2>/dev/null || true; '
|
|
123
|
+
"fi; "
|
|
124
|
+
"}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
slurm_parameters.startup_commands.append("trap cleanup EXIT INT TERM")
|
|
128
|
+
toml_path = context.config.project_root / "simstack_docker.toml"
|
|
129
|
+
if not toml_path.exists():
|
|
130
|
+
logger.error(
|
|
131
|
+
f"Task task_id: {task_id} has no simstack.toml file -- failing"
|
|
132
|
+
)
|
|
133
|
+
registry_entry.status = TaskStatus.FAILED
|
|
134
|
+
await context.db.save(registry_entry)
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
docker_start = "udocker run "
|
|
138
|
+
docker_start += "-e GIT_TOKEN=XXX"
|
|
139
|
+
docker_start += f"-e NODE_ID={registry_entry.id} "
|
|
140
|
+
docker_start += f"-e RESOURCE={str(context.config.resource)} "
|
|
141
|
+
docker_start += f"-v {external_work_dir}:/home/appuser/simstack "
|
|
142
|
+
docker_start += (
|
|
143
|
+
f"-v {context.config.external_source_dir}:/app/simstack-model "
|
|
144
|
+
)
|
|
145
|
+
docker_start += f"-v {toml_path}:/app/simstack-model/simstack.toml "
|
|
146
|
+
docker_start += " simstack-runner"
|
|
147
|
+
slurm_parameters.startup_commands.append(docker_start)
|
|
148
|
+
else:
|
|
149
|
+
slurm_parameters.startup_commands.append(
|
|
150
|
+
f"uv run --directory {base_path} run_node --node-id {registry_entry.id} --resource {str(context.config.resource)} &"
|
|
151
|
+
)
|
|
152
|
+
slurm_parameters.startup_commands.append("wait")
|
|
153
|
+
slurm_parameters.signal = "B:SIGUSR1@60"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
slurm_script = slurm_parameters.to_sbatch_header()
|
|
157
|
+
|
|
158
|
+
# write the script to a file in the work_dir
|
|
159
|
+
os.makedirs(work_dir, exist_ok=True)
|
|
160
|
+
logger.info(f"task_id: {task_id} workdir {work_dir} python path {python_path}")
|
|
161
|
+
script_path = os.path.join(work_dir, "slurm_script.sh")
|
|
162
|
+
with open(script_path, "w") as f:
|
|
163
|
+
f.write(slurm_script)
|
|
164
|
+
|
|
165
|
+
make_executable(script_path)
|
|
166
|
+
# submit the script to the slurm queue
|
|
167
|
+
if context.config.docker:
|
|
168
|
+
job_id = "slurm_" + str(registry_entry.id)
|
|
169
|
+
queue_dir = context.config.workdir / "queue"
|
|
170
|
+
result = submit_to_watchdog(
|
|
171
|
+
f"/usr/bin/sbatch {os.path.join(external_work_dir, 'slurm_script.sh')}",
|
|
172
|
+
job_id,
|
|
173
|
+
queue_dir,
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
result = subprocess.run(
|
|
177
|
+
f"/usr/bin/sbatch {os.path.join(work_dir, 'slurm_script.sh')}",
|
|
178
|
+
shell=True,
|
|
179
|
+
capture_output=True,
|
|
180
|
+
text=True,
|
|
181
|
+
timeout=30, # Add timeout to prevent hanging
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
logger.info(
|
|
185
|
+
f"submitting job task_id: {task_id} returns: {result.returncode} {result.stdout} {result.stderr}"
|
|
186
|
+
)
|
|
187
|
+
if result.returncode == 0:
|
|
188
|
+
# Extract job ID using a regex pattern
|
|
189
|
+
match = re.search(r"Submitted batch job (\d+)", result.stdout)
|
|
190
|
+
if match:
|
|
191
|
+
job_id = match.group(1)
|
|
192
|
+
logger.info(
|
|
193
|
+
f"task_id: {task_id} job successfully submitted with job_id: {job_id}"
|
|
194
|
+
)
|
|
195
|
+
registry_entry.job_id = job_id
|
|
196
|
+
else:
|
|
197
|
+
logger.warning(
|
|
198
|
+
f"task_id: {task_id} job submitted but could not extract job_id from output: {result.stdout}"
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
logger.error(
|
|
202
|
+
f"error submitting job for task_id: {task_id} return code: {result.returncode} stdout: {result.stdout}"
|
|
203
|
+
)
|
|
204
|
+
logger.error(
|
|
205
|
+
f"submitting job for task_id: {task_id} stderr: {result.stderr}"
|
|
206
|
+
)
|
|
207
|
+
registry_entry.status = TaskStatus.FAILED
|
|
208
|
+
await context.db.save(registry_entry)
|
|
209
|
+
return False
|
|
210
|
+
registry_entry.status = TaskStatus.SLURM_QUEUED
|
|
211
|
+
await context.db.save(registry_entry)
|
|
212
|
+
return True
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.exception(f"fatal error in submitting task_id: {task_id} {str(e)}")
|
|
215
|
+
registry_entry.status = TaskStatus.FAILED
|
|
216
|
+
await context.db.save(registry_entry)
|
|
217
|
+
return False
|
simstack/core/task_id.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from contextvars import ContextVar
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from bson.objectid import ObjectId
|
|
5
|
+
|
|
6
|
+
# this variable is meant to give access to the task id to user functions that do not have access to it (e.g. for logging)
|
|
7
|
+
_task_id: ContextVar[Optional[ObjectId]] = ContextVar("task_id", default=None)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_task_id() -> Optional[ObjectId]:
|
|
11
|
+
"""Get the current task ID."""
|
|
12
|
+
return _task_id.get()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def set_task_id(task_id: Optional[ObjectId]) -> None:
|
|
16
|
+
"""Set the current task ID."""
|
|
17
|
+
_task_id.set(task_id)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def clear_task_id() -> None:
|
|
21
|
+
"""Clear the current task ID."""
|
|
22
|
+
_task_id.set(None)
|
|
File without changes
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Disabled archive prototype retained for a future, separately reviewed change.
|
|
2
|
+
|
|
3
|
+
The public entrypoints intentionally fail closed. Permanent storage ownership and
|
|
4
|
+
safe source cleanup must be designed and covered by production-path tests before
|
|
5
|
+
this module can be enabled.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
import shutil
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from odmantic import Model, Field
|
|
15
|
+
|
|
16
|
+
from simstack.core.context import context
|
|
17
|
+
from simstack.core.node import node
|
|
18
|
+
from simstack.models import FileStack, FileList, simstack_model
|
|
19
|
+
from simstack.models.base_lists import BooleanDataList
|
|
20
|
+
from simstack.models.file_instance import FileInstance
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ARCHIVE_DISABLED_MESSAGE = (
|
|
24
|
+
"File archiving is disabled for this migration until permanent-resource "
|
|
25
|
+
"semantics and non-destructive cleanup are implemented and tested."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ArchiveFeatureDisabledError(RuntimeError):
|
|
30
|
+
"""Raised when code tries to use the excluded archive prototype."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _raise_archive_disabled() -> None:
|
|
34
|
+
raise ArchiveFeatureDisabledError(ARCHIVE_DISABLED_MESSAGE)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def archive_one_file(file_stack: FileStack,**kwargs):
|
|
38
|
+
_raise_archive_disabled()
|
|
39
|
+
node_runner = kwargs["node_runner"]
|
|
40
|
+
node_runner.info(f"Archiving file: {file_stack.name}")
|
|
41
|
+
|
|
42
|
+
config = context.resource_config.get_program("archive_one_file")
|
|
43
|
+
if config.get("archive_path", None) is None:
|
|
44
|
+
raise ValueError("archive_path not set in config")
|
|
45
|
+
archive_path = Path(config.get("archive_path"))
|
|
46
|
+
full_path = archive_path / str(file_stack.id)
|
|
47
|
+
full_path.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
if full_path.exists() and full_path.is_dir():
|
|
49
|
+
try:
|
|
50
|
+
archive_location = file_stack.get(local_dir=full_path)
|
|
51
|
+
archive_file_instance = FileInstance.from_local_file(archive_location)
|
|
52
|
+
node_runner.info(f"Archived file to {archive_file_instance.path}")
|
|
53
|
+
file_stack.locations.append(archive_file_instance)
|
|
54
|
+
return True
|
|
55
|
+
except Exception as e:
|
|
56
|
+
node_runner.error(f"Error archiving file: {str(e)}")
|
|
57
|
+
return False
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"Archive path {archive_path} does not exist or is not a directory")
|
|
60
|
+
|
|
61
|
+
@node
|
|
62
|
+
async def archive_file(file_stack: FileStack,**kwargs):
|
|
63
|
+
_raise_archive_disabled()
|
|
64
|
+
return archive_one_file(file_stack, **kwargs)
|
|
65
|
+
|
|
66
|
+
@node
|
|
67
|
+
def archive_files(file_list: FileList,**kwargs):
|
|
68
|
+
_raise_archive_disabled()
|
|
69
|
+
archive_results = BooleanDataList(field_name="archive_result")
|
|
70
|
+
for file_stack in file_list:
|
|
71
|
+
archive_results.append(archive_one_file(file_stack, **kwargs))
|
|
72
|
+
return archive_results
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@simstack_model
|
|
76
|
+
class ArchiveConfig(Model):
|
|
77
|
+
call_paths: List[str] = Field(default_factory=list)
|
|
78
|
+
archive_resource: str = Field(default="archive_resource", description="Name of the resource where the files will be archived")
|
|
79
|
+
in_memory: bool = Field(default=False, description="Whether to archive files in memory")
|
|
80
|
+
use_time_window: bool = Field(default=False, description="Whether to archive files within a time window")
|
|
81
|
+
start_date: Optional[datetime] = Field(default=None, description="Start of the period to archive files for")
|
|
82
|
+
end_date: Optional[datetime] = Field(default=None, description="End end of the period to archive files for")
|
|
83
|
+
include_patterns: List[str] = Field(default_factory=list, description="List of file patterns to include")
|
|
84
|
+
exclude_patterns: List[str] = Field(default_factory=list, description="List of file patterns to exclude")
|
|
85
|
+
min_size: int = Field(default=0, description="Minimum size of files to archive")
|
|
86
|
+
filter_by_resource: bool = Field(default=False, description="Whether to only archive files that have a location on the current resource")
|
|
87
|
+
|
|
88
|
+
model_config = {"collection": "archive_config"}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@node
|
|
92
|
+
async def archive_node(archive_config: ArchiveConfig, **kwargs):
|
|
93
|
+
"""
|
|
94
|
+
Archives FileStacks matching the criteria in archive_config and deletes local instances upon success.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
archive_config (ArchiveConfig): Configuration for the archive process.
|
|
98
|
+
**kwargs: Additional arguments.
|
|
99
|
+
"""
|
|
100
|
+
_raise_archive_disabled()
|
|
101
|
+
|
|
102
|
+
with open("TEST_EXECUTION.log", "a") as f:
|
|
103
|
+
f.write(f"archive_node CALLED at {datetime.now()}\n")
|
|
104
|
+
|
|
105
|
+
node_runner = kwargs["node_runner"]
|
|
106
|
+
db = context.db
|
|
107
|
+
my_resource = context.config.resource
|
|
108
|
+
|
|
109
|
+
# Discover FileStacks
|
|
110
|
+
all_file_stacks = []
|
|
111
|
+
import sys
|
|
112
|
+
sys.stderr.write(f"DEBUG: Discovery starting for resource {my_resource}\n")
|
|
113
|
+
sys.stderr.flush()
|
|
114
|
+
if archive_config.call_paths:
|
|
115
|
+
from simstack.models import NodeRegistry, FileListModel
|
|
116
|
+
|
|
117
|
+
# Find matching NodeRegistry entries
|
|
118
|
+
node_query = {
|
|
119
|
+
"call_path": {"$in": archive_config.call_paths},
|
|
120
|
+
"parameters.resource.value": my_resource
|
|
121
|
+
}
|
|
122
|
+
matching_nodes = await db.find(NodeRegistry, node_query)
|
|
123
|
+
|
|
124
|
+
# We also need mappings to know which collection to look into
|
|
125
|
+
# context.model_mappings has by_mapping and by_name
|
|
126
|
+
filestack_mapping = context.model_mappings.get_by_name("FileStack")
|
|
127
|
+
filelist_mapping = context.model_mappings.get_by_name("FileList")
|
|
128
|
+
filelistmodel_mapping = context.model_mappings.get_by_name("FileListModel")
|
|
129
|
+
|
|
130
|
+
fs_mapping_str = filestack_mapping.mapping if filestack_mapping else ""
|
|
131
|
+
fl_mapping_str = filelist_mapping.mapping if filelist_mapping else ""
|
|
132
|
+
flm_mapping_str = filelistmodel_mapping.mapping if filelistmodel_mapping else ""
|
|
133
|
+
|
|
134
|
+
found_fs_ids = set()
|
|
135
|
+
found_fl_ids = set()
|
|
136
|
+
found_flm_ids = set()
|
|
137
|
+
|
|
138
|
+
for node in matching_nodes:
|
|
139
|
+
# Check info_files
|
|
140
|
+
for fs in node.info_files:
|
|
141
|
+
found_fs_ids.add(fs.id)
|
|
142
|
+
|
|
143
|
+
# Check input_references and results_references
|
|
144
|
+
for ref in node.input_references + node.results_references:
|
|
145
|
+
if ref.variable_mapping == fs_mapping_str:
|
|
146
|
+
found_fs_ids.add(ref.reference)
|
|
147
|
+
elif ref.variable_mapping == fl_mapping_str:
|
|
148
|
+
found_fl_ids.add(ref.reference)
|
|
149
|
+
elif ref.variable_mapping == flm_mapping_str:
|
|
150
|
+
found_flm_ids.add(ref.reference)
|
|
151
|
+
|
|
152
|
+
# Retrieve FileStacks from found IDs
|
|
153
|
+
if found_fs_ids:
|
|
154
|
+
file_stacks = await db.find(FileStack, {"_id": {"$in": list(found_fs_ids)}})
|
|
155
|
+
all_file_stacks.extend(file_stacks)
|
|
156
|
+
|
|
157
|
+
# Retrieve FileLists and their FileStacks
|
|
158
|
+
if found_flm_ids:
|
|
159
|
+
file_list_models = await db.find(FileListModel, {"_id": {"$in": list(found_flm_ids)}})
|
|
160
|
+
for flm in file_list_models:
|
|
161
|
+
# FileListModel uses ObjectListMixin[FileStack], elements are ObjectIds
|
|
162
|
+
# We need to fetch the actual FileStack objects.
|
|
163
|
+
if flm.elements:
|
|
164
|
+
stacks = await db.find(FileStack, {"_id": {"$in": flm.elements}})
|
|
165
|
+
all_file_stacks.extend(stacks)
|
|
166
|
+
|
|
167
|
+
# Deduplicate all_file_stacks by ID
|
|
168
|
+
unique_stacks = {}
|
|
169
|
+
for fs in all_file_stacks:
|
|
170
|
+
unique_stacks[fs.id] = fs
|
|
171
|
+
all_file_stacks = list(unique_stacks.values())
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
# Existing logic: Query FileStack collection directly
|
|
175
|
+
query = {}
|
|
176
|
+
if archive_config.min_size > 0:
|
|
177
|
+
query["size"] = {"$gte": archive_config.min_size}
|
|
178
|
+
|
|
179
|
+
if archive_config.filter_by_resource:
|
|
180
|
+
query["locations.resource.value"] = my_resource
|
|
181
|
+
|
|
182
|
+
all_file_stacks = await db.find(FileStack, query)
|
|
183
|
+
|
|
184
|
+
print(f"DEBUG: Found {len(all_file_stacks)} FileStacks")
|
|
185
|
+
filtered_file_stacks = []
|
|
186
|
+
|
|
187
|
+
for fs in all_file_stacks:
|
|
188
|
+
# Filter by date if specified
|
|
189
|
+
# Since FileStack doesn't have a date, we check its locations.
|
|
190
|
+
if archive_config.start_date or archive_config.end_date:
|
|
191
|
+
valid_date = False
|
|
192
|
+
for loc in fs.locations:
|
|
193
|
+
if loc.created_at:
|
|
194
|
+
if archive_config.start_date and loc.created_at < archive_config.start_date:
|
|
195
|
+
continue
|
|
196
|
+
if archive_config.end_date and loc.created_at > archive_config.end_date:
|
|
197
|
+
continue
|
|
198
|
+
valid_date = True
|
|
199
|
+
break
|
|
200
|
+
if not valid_date:
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Filter by patterns
|
|
204
|
+
if archive_config.include_patterns:
|
|
205
|
+
import fnmatch
|
|
206
|
+
if not any(fnmatch.fnmatch(fs.name, pat) for pat in archive_config.include_patterns):
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
if archive_config.exclude_patterns:
|
|
210
|
+
import fnmatch
|
|
211
|
+
if any(fnmatch.fnmatch(fs.name, pat) for pat in archive_config.exclude_patterns):
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
filtered_file_stacks.append(fs)
|
|
215
|
+
|
|
216
|
+
if not filtered_file_stacks:
|
|
217
|
+
node_runner.info("No files found matching the archival criteria.")
|
|
218
|
+
return BooleanDataList(field_name="archive_results")
|
|
219
|
+
|
|
220
|
+
# 2. Add them to FileList
|
|
221
|
+
archive_file_list = FileList(field_name="archive_files")
|
|
222
|
+
for fs in filtered_file_stacks:
|
|
223
|
+
archive_file_list.append(fs)
|
|
224
|
+
|
|
225
|
+
print(f"DEBUG: archive_file_list has {len(archive_file_list)} files")
|
|
226
|
+
# 3. Call archive_files on the filelist
|
|
227
|
+
# Note: archive_files is a @node, we can call it directly.
|
|
228
|
+
# It returns a BooleanDataList
|
|
229
|
+
|
|
230
|
+
# To handle mocks and different return types, we ensure we get a BooleanDataList
|
|
231
|
+
# When calling a @node decorated function, it returns a SimstackResult or Model in sync mode,
|
|
232
|
+
# or the result of the function if it was run locally.
|
|
233
|
+
# If we are in the same process and calling it, it might be running via NodeRunner.
|
|
234
|
+
raw_results = await archive_files(archive_file_list, **kwargs)
|
|
235
|
+
print(f"DEBUG: raw_results type={type(raw_results)}")
|
|
236
|
+
if isinstance(raw_results, BooleanDataList):
|
|
237
|
+
archive_results = raw_results
|
|
238
|
+
else:
|
|
239
|
+
# If mocked or returned as list/iterable
|
|
240
|
+
archive_results = BooleanDataList(field_name="archive_results")
|
|
241
|
+
from simstack.models import BooleanData
|
|
242
|
+
if raw_results is not None:
|
|
243
|
+
# Handle possible async result if not awaited properly by decorator
|
|
244
|
+
if inspect.isawaitable(raw_results):
|
|
245
|
+
raw_results = await raw_results
|
|
246
|
+
|
|
247
|
+
for r in raw_results:
|
|
248
|
+
if isinstance(r, bool):
|
|
249
|
+
archive_results.append(BooleanData(value=r))
|
|
250
|
+
elif isinstance(r, BooleanData):
|
|
251
|
+
archive_results.append(r)
|
|
252
|
+
else:
|
|
253
|
+
# try to save it if it's a model
|
|
254
|
+
archive_results.append(r)
|
|
255
|
+
|
|
256
|
+
# 4. Delete local FileInstances for successfull archival
|
|
257
|
+
# archive_results is a BooleanDataList, elements are BooleanData (presumably)
|
|
258
|
+
# Wait, archive_files returns BooleanDataList. Let's check BooleanDataList implementation.
|
|
259
|
+
|
|
260
|
+
# Actually archive_files returns BooleanDataList where each element corresponds to a FileStack in archive_file_list
|
|
261
|
+
for i, success_data in enumerate(archive_results):
|
|
262
|
+
if success_data.value:
|
|
263
|
+
fs = archive_file_list[i]
|
|
264
|
+
# Delete local FileInstances
|
|
265
|
+
# We need to know which ones are "local".
|
|
266
|
+
# Usually those are on the current resource.
|
|
267
|
+
current_resource = context.config.resource
|
|
268
|
+
|
|
269
|
+
remaining_locations = []
|
|
270
|
+
for loc in fs.locations:
|
|
271
|
+
# If it's on the current resource and is a local path, delete it
|
|
272
|
+
if loc.resource.value == current_resource and loc.location_type == "local_path":
|
|
273
|
+
try:
|
|
274
|
+
local_path = Path(context.config.workdir) / loc.path
|
|
275
|
+
if local_path.exists():
|
|
276
|
+
if local_path.is_file():
|
|
277
|
+
local_path.unlink()
|
|
278
|
+
elif local_path.is_dir():
|
|
279
|
+
shutil.rmtree(local_path)
|
|
280
|
+
node_runner.info(f"Deleted local instance of {fs.name} at {local_path}")
|
|
281
|
+
else:
|
|
282
|
+
node_runner.warning(f"Local path {local_path} for {fs.name} does not exist")
|
|
283
|
+
except Exception as e:
|
|
284
|
+
node_runner.error(f"Failed to delete local instance of {fs.name}: {e}")
|
|
285
|
+
remaining_locations.append(loc)
|
|
286
|
+
else:
|
|
287
|
+
remaining_locations.append(loc)
|
|
288
|
+
|
|
289
|
+
fs.locations = remaining_locations
|
|
290
|
+
await db.save(fs)
|
|
291
|
+
|
|
292
|
+
return archive_results
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
from typing import List, Callable, Any, Optional
|
|
4
|
+
|
|
5
|
+
from odmantic import Model
|
|
6
|
+
|
|
7
|
+
from simstack.core.context import context
|
|
8
|
+
from simstack.core.definitions import TaskStatus
|
|
9
|
+
from simstack.core.simstack_result import SimstackResult
|
|
10
|
+
from simstack.core.node_runner import NodeRunner
|
|
11
|
+
from simstack.core.process_results import process_result_helper
|
|
12
|
+
from simstack.models import FireAndForgetResult
|
|
13
|
+
|
|
14
|
+
class FireAndForgetRunner(NodeRunner):
|
|
15
|
+
def __init__(self, node: Callable[..., Any], max_concurrency: Optional[int] = None, **kwargs):
|
|
16
|
+
# We need to handle the case where "node_runner" might be in kwargs, just like MassRunner does.
|
|
17
|
+
# However, NodeRunner.__init__ expects (name, task_id, logger).
|
|
18
|
+
# MassRunner does: super().__init__(kwargs["node_runner"].name, kwargs["node_runner"].task_id, kwargs["node_runner"].logger )
|
|
19
|
+
|
|
20
|
+
node_runner = kwargs.get("node_runner")
|
|
21
|
+
if node_runner:
|
|
22
|
+
super().__init__(node_runner.name, node_runner.task_id, node_runner.logger)
|
|
23
|
+
self.call_path = getattr(node_runner, "call_path", "NA")
|
|
24
|
+
else:
|
|
25
|
+
# Fallback if not provided, though it's expected in this context
|
|
26
|
+
super().__init__(kwargs.get("name", "FireAndForgetRunner"), kwargs.get("task_id", "NA"), kwargs.get("logger"))
|
|
27
|
+
self.call_path = "NA"
|
|
28
|
+
|
|
29
|
+
self._node = node
|
|
30
|
+
self._max_concurrency = max_concurrency
|
|
31
|
+
self._semaphore = asyncio.Semaphore(max_concurrency) if max_concurrency else None
|
|
32
|
+
self._tasks = []
|
|
33
|
+
self._kwargs = kwargs.copy()
|
|
34
|
+
|
|
35
|
+
async def _run_node(self, args: List[Model]):
|
|
36
|
+
sig = inspect.signature(self._node)
|
|
37
|
+
param_names = list(sig.parameters.keys())
|
|
38
|
+
|
|
39
|
+
node_name = getattr(self._node, "__name__", "unknown_node")
|
|
40
|
+
full_call_path = f"{self.call_path}/{node_name}"
|
|
41
|
+
|
|
42
|
+
models_dict = {}
|
|
43
|
+
for i, m in enumerate(args):
|
|
44
|
+
if i < len(param_names):
|
|
45
|
+
models_dict[f"arg_{param_names[i]}"] = m
|
|
46
|
+
else:
|
|
47
|
+
models_dict[f"arg_{i}"] = m
|
|
48
|
+
|
|
49
|
+
success = False
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
if self._semaphore:
|
|
53
|
+
async with self._semaphore:
|
|
54
|
+
if asyncio.iscoroutinefunction(self._node):
|
|
55
|
+
result = await self._node(*args, **self._kwargs)
|
|
56
|
+
else:
|
|
57
|
+
result = self._node(*args, **self._kwargs)
|
|
58
|
+
else:
|
|
59
|
+
if asyncio.iscoroutinefunction(self._node):
|
|
60
|
+
result = await self._node(*args, **self._kwargs)
|
|
61
|
+
else:
|
|
62
|
+
result = self._node(*args, **self._kwargs)
|
|
63
|
+
|
|
64
|
+
if result is None:
|
|
65
|
+
success = False
|
|
66
|
+
elif isinstance(result, bool):
|
|
67
|
+
success = result
|
|
68
|
+
elif isinstance(result, (SimstackResult, Model)):
|
|
69
|
+
references, models = await process_result_helper(result, self.task_id)
|
|
70
|
+
if isinstance(result, SimstackResult):
|
|
71
|
+
success = (result.status == TaskStatus.COMPLETED)
|
|
72
|
+
else:
|
|
73
|
+
success = True
|
|
74
|
+
|
|
75
|
+
if references:
|
|
76
|
+
for reference, model in zip(references, models):
|
|
77
|
+
models_dict[f"result_{reference.variable_name}"] = model
|
|
78
|
+
elif isinstance(result, (list, tuple)) and all(isinstance(m, Model) for m in result):
|
|
79
|
+
success = True
|
|
80
|
+
for i, m in enumerate(result):
|
|
81
|
+
models_dict[f"result_{i}"] = m
|
|
82
|
+
else:
|
|
83
|
+
# Other types of results
|
|
84
|
+
success = True
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self.error(f"Error running node in FireAndForgetRunner: {e}")
|
|
88
|
+
success = False
|
|
89
|
+
models_dict["error"] = str(e)
|
|
90
|
+
|
|
91
|
+
# Immediately write to DB
|
|
92
|
+
ff_result = FireAndForgetResult(
|
|
93
|
+
call_path=full_call_path,
|
|
94
|
+
models=models_dict,
|
|
95
|
+
success=success
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
db = context.db
|
|
99
|
+
await db.save(ff_result)
|
|
100
|
+
return ff_result
|
|
101
|
+
|
|
102
|
+
def create_tasks(self, *args: Model):
|
|
103
|
+
task = asyncio.create_task(self._run_node(list(args)))
|
|
104
|
+
self._tasks.append(task)
|
|
105
|
+
return task
|
|
106
|
+
|
|
107
|
+
async def __aenter__(self):
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
111
|
+
if self._tasks:
|
|
112
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
113
|
+
return self.succeed()
|