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,198 @@
|
|
|
1
|
+
import tomllib
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Any, Optional, List, Union
|
|
9
|
+
|
|
10
|
+
from odmantic import ObjectId
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
logger = logging.getLogger("ResourceConfig")
|
|
14
|
+
|
|
15
|
+
class ResourceConfig:
|
|
16
|
+
"""
|
|
17
|
+
ResourceConfig is responsible for managing configuration settings, setup,
|
|
18
|
+
execution, and post-processing parameters for specified resources.
|
|
19
|
+
|
|
20
|
+
This class is designed to read configuration data from a TOML file, provide
|
|
21
|
+
an interface to access resource-specific parameters, and execute resource-related
|
|
22
|
+
operations such as setup and running commands. It encapsulates functionality for
|
|
23
|
+
handling temporary directories, file manipulation, and subprocess execution.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
_config (Dict[str, Any]): The loaded configuration dictionary.
|
|
27
|
+
_resource (str): The name of the current resource.
|
|
28
|
+
"""
|
|
29
|
+
def __init__(self, config_path: Path, resource: str):
|
|
30
|
+
self._config: Dict[str, Any] = {}
|
|
31
|
+
self._resource = resource
|
|
32
|
+
|
|
33
|
+
actual_path = Path(config_path)
|
|
34
|
+
if actual_path.is_dir():
|
|
35
|
+
actual_path = actual_path / "config.toml"
|
|
36
|
+
|
|
37
|
+
if actual_path.exists():
|
|
38
|
+
with open(actual_path, "rb") as f:
|
|
39
|
+
self._config = tomllib.load(f)
|
|
40
|
+
else:
|
|
41
|
+
# If it doesn't exist, we just have an empty config
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def os(self) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Returns the OS of the current resource, defaults to 'linux'.
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
return self._config[self._resource].get("os", "linux")
|
|
51
|
+
except KeyError:
|
|
52
|
+
return "linux"
|
|
53
|
+
|
|
54
|
+
def setup(self, node_runner: Optional[Any] = None):
|
|
55
|
+
"""
|
|
56
|
+
Executes the setup scripts for the current resource.
|
|
57
|
+
"""
|
|
58
|
+
setup_params = self.get_setup_params()
|
|
59
|
+
scripts = setup_params.get("scripts", [])
|
|
60
|
+
for i, script in enumerate(scripts):
|
|
61
|
+
# Note: shell=True is needed to execute shell commands/scripts
|
|
62
|
+
# and might be OS dependent for the actual commands.
|
|
63
|
+
if node_runner and hasattr(node_runner, "subprocess"):
|
|
64
|
+
node_runner.subprocess(f"setup_{i}", script)
|
|
65
|
+
else:
|
|
66
|
+
subprocess.run(script, shell=True, check=True)
|
|
67
|
+
|
|
68
|
+
def tmp_dir(self, task_id: ObjectId | str) -> Path:
|
|
69
|
+
if isinstance(task_id, ObjectId):
|
|
70
|
+
task_id = str(task_id)
|
|
71
|
+
tmp_dir = self.tmp_base_dir / str(task_id)
|
|
72
|
+
Path(tmp_dir).mkdir(parents=True, exist_ok=True)
|
|
73
|
+
return tmp_dir
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def tmp_base_dir(self) -> Path:
|
|
77
|
+
tmp_base_dir_str = self.get_setup_params().get("tmp_base_dir", "")
|
|
78
|
+
|
|
79
|
+
if tmp_base_dir_str:
|
|
80
|
+
expanded_path_str = os.path.expandvars(os.path.expanduser(tmp_base_dir_str))
|
|
81
|
+
path = Path(expanded_path_str)
|
|
82
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
return path
|
|
84
|
+
|
|
85
|
+
# Default to system temp directory if no command/path specified
|
|
86
|
+
return Path(tempfile.gettempdir())
|
|
87
|
+
|
|
88
|
+
def run(self,
|
|
89
|
+
program_name: str,
|
|
90
|
+
input_files: Optional[List[Union[str, "FileStack"]]] = None,
|
|
91
|
+
output_files: Optional[List[Union[str, "FileStack"]]] = None,
|
|
92
|
+
node_runner: Optional[Any] = None,
|
|
93
|
+
):
|
|
94
|
+
"""
|
|
95
|
+
Executes the run command with optional temporary directory usage and file handling.
|
|
96
|
+
Retrieves parameters from the configuration for the specified program.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
program_name: Name of the program to run.
|
|
100
|
+
input_files: List of input files (str or FileStack). Overrides TOML input_files if provided.
|
|
101
|
+
output_files: List of output files (str or FileStack). Overrides TOML output_files if provided.
|
|
102
|
+
node_runner: Optional NodeRunner instance for execution.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
params = self.get_program(program_name)
|
|
106
|
+
run_command = params.get("run_command", "")
|
|
107
|
+
|
|
108
|
+
if input_files is None:
|
|
109
|
+
input_files = params.get("input_files", [])
|
|
110
|
+
|
|
111
|
+
if output_files is None:
|
|
112
|
+
output_files = params.get("output_files", [])
|
|
113
|
+
|
|
114
|
+
use_temp = params.get("use_temp", False)
|
|
115
|
+
|
|
116
|
+
# tmp_base_dir can come from setup or the program itself, but usually it's in setup for the resource
|
|
117
|
+
|
|
118
|
+
# scratch_cleanup from postprocessing
|
|
119
|
+
post_params = self.get_postprocessing_params()
|
|
120
|
+
scratch_cleanup = params.get("scratch_cleanup", post_params.get("scratch_cleanup", False))
|
|
121
|
+
|
|
122
|
+
tmp_dir = None
|
|
123
|
+
try:
|
|
124
|
+
exec_dir = Path.cwd()
|
|
125
|
+
if use_temp:
|
|
126
|
+
tmp_id = node_runner.task_id if node_runner else uuid.uuid4()
|
|
127
|
+
tmp_dir = self.tmp_dir(tmp_id)
|
|
128
|
+
exec_dir = tmp_dir
|
|
129
|
+
|
|
130
|
+
# Copy input files to exec_dir
|
|
131
|
+
for f in input_files:
|
|
132
|
+
if hasattr(f, "get"): # It's a FileStack
|
|
133
|
+
f.get(local_dir=exec_dir)
|
|
134
|
+
else: # It's a string (filename)
|
|
135
|
+
src = Path.cwd() / f
|
|
136
|
+
if src.exists() and src != exec_dir / f:
|
|
137
|
+
shutil.copy(src, exec_dir / f)
|
|
138
|
+
|
|
139
|
+
# Execute run_command
|
|
140
|
+
if node_runner and hasattr(node_runner, "subprocess"):
|
|
141
|
+
node_runner.subprocess("run", run_command, cwd=str(exec_dir))
|
|
142
|
+
else:
|
|
143
|
+
subprocess.run(run_command, shell=True, check=True, cwd=exec_dir)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Copy output files back to cwd
|
|
147
|
+
if use_temp and tmp_dir:
|
|
148
|
+
for f in output_files:
|
|
149
|
+
# output_files can also be FileStack in the new pattern?
|
|
150
|
+
# "output_files: List[str | FileStack] = [...]"
|
|
151
|
+
# If it's a FileStack, we might need to update its content from the local file
|
|
152
|
+
# but the standard behavior for output_files here is copying back to cwd.
|
|
153
|
+
filename = f.name if hasattr(f, "name") else f
|
|
154
|
+
src = tmp_dir / filename
|
|
155
|
+
if src.exists():
|
|
156
|
+
shutil.copy(src, Path.cwd() / filename)
|
|
157
|
+
|
|
158
|
+
finally:
|
|
159
|
+
if scratch_cleanup and tmp_dir and tmp_dir.exists():
|
|
160
|
+
shutil.rmtree(tmp_dir)
|
|
161
|
+
|
|
162
|
+
def get_program(self, program_name: str) -> Dict[str, Any]:
|
|
163
|
+
"""
|
|
164
|
+
Returns the dict from resource.program.name for program with name and the current resource.
|
|
165
|
+
Expected structure in TOML: [resource_name.program.program_name]
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
return self._config[self._resource]["program"][program_name]
|
|
169
|
+
except (KeyError, TypeError):
|
|
170
|
+
return {}
|
|
171
|
+
|
|
172
|
+
def get_setup_params(self) -> Dict[str, Any]:
|
|
173
|
+
"""
|
|
174
|
+
Returns the setup dict for the specified resource.
|
|
175
|
+
Expected structure in TOML: [resource_name.setup]
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
return self._config[self._resource]["setup"]
|
|
179
|
+
except KeyError:
|
|
180
|
+
return {}
|
|
181
|
+
|
|
182
|
+
def get_postprocessing_params(self) -> Dict[str, Any]:
|
|
183
|
+
"""
|
|
184
|
+
Returns the post-processing dict for the specified resource.
|
|
185
|
+
Expected structure in TOML: [resource_name.post-processing] or [resource_name.postprocessing]
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
resource_cfg = self._config[self._resource]
|
|
189
|
+
if "post-processing" in resource_cfg:
|
|
190
|
+
return resource_cfg["post-processing"]
|
|
191
|
+
if "postprocessing" in resource_cfg:
|
|
192
|
+
return resource_cfg["postprocessing"]
|
|
193
|
+
except KeyError:
|
|
194
|
+
pass
|
|
195
|
+
return {}
|
|
196
|
+
|
|
197
|
+
def __str__(self):
|
|
198
|
+
return f"ResourceConfig(resource={self._resource}, config={self._config})"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# TODO rmeove duplicate route finder this is a test implementation
|
|
2
|
+
routes_table = [
|
|
3
|
+
{"target": "local", "source": "int-nano", "host": "local"},
|
|
4
|
+
{"target": "int-nano", "source": "local", "host": "local"},
|
|
5
|
+
{
|
|
6
|
+
"target": "horeka",
|
|
7
|
+
"source": "local",
|
|
8
|
+
"host": "horeka",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"target": "local",
|
|
12
|
+
"source": "horeka",
|
|
13
|
+
"host": "horeka",
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_route(target, source):
|
|
19
|
+
"""
|
|
20
|
+
Find the minimal route from source to target using available routes.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
target (str): The target system
|
|
24
|
+
source (str): The source system
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
list: A list of systems to visit in order, or None if no route exists
|
|
28
|
+
"""
|
|
29
|
+
# If source and target are the same, no transfer needed
|
|
30
|
+
if source == target:
|
|
31
|
+
return [source]
|
|
32
|
+
|
|
33
|
+
# Build a graph of available routes
|
|
34
|
+
graph = {}
|
|
35
|
+
for route in routes_table:
|
|
36
|
+
if route["source"] not in graph:
|
|
37
|
+
graph[route["source"]] = []
|
|
38
|
+
graph[route["source"]].append(route["target"])
|
|
39
|
+
|
|
40
|
+
# If source isn't in our graph, we can't start a route
|
|
41
|
+
if source not in graph:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# Use BFS to find the shortest path
|
|
45
|
+
visited = {source}
|
|
46
|
+
queue = [(source, [source])] # (current_node, path_so_far)
|
|
47
|
+
|
|
48
|
+
while queue:
|
|
49
|
+
current, path = queue.pop(0)
|
|
50
|
+
|
|
51
|
+
# Check neighbors (available transfer targets)
|
|
52
|
+
if current in graph:
|
|
53
|
+
for neighbor in graph[current]:
|
|
54
|
+
if neighbor == target:
|
|
55
|
+
# Found a path to target
|
|
56
|
+
return path + [neighbor]
|
|
57
|
+
|
|
58
|
+
if neighbor not in visited:
|
|
59
|
+
visited.add(neighbor)
|
|
60
|
+
queue.append((neighbor, path + [neighbor]))
|
|
61
|
+
|
|
62
|
+
# If we've exhausted all possibilities without finding a path
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
# Example usage
|
|
68
|
+
target = "int-nano"
|
|
69
|
+
source = "horeka"
|
|
70
|
+
route = find_route(target, source)
|
|
71
|
+
if route:
|
|
72
|
+
print(f"Route from {source} to {target}: {' -> '.join(route)}")
|
|
73
|
+
else:
|
|
74
|
+
print(f"No route found from {source} to {target}")
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import socket
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from simstack.core.context import context
|
|
10
|
+
from simstack.models.runner_model import RunnerEvent, RunnerType, RunnerEventEnum
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("RunnerUtil")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def ensure_crontab_entry(command, schedule="*/10 * * * *"):
|
|
16
|
+
"""
|
|
17
|
+
Checks if a crontab entry exists and adds it if it doesn't.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
command (str): The command to be executed by cron
|
|
21
|
+
schedule (str): The cron schedule expression (default: every 10 minutes)
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
bool: True if entry was added, False if it already existed
|
|
25
|
+
"""
|
|
26
|
+
# The full crontab entry to look for
|
|
27
|
+
cron_entry = f"{schedule} {command}"
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
# Get current crontab
|
|
31
|
+
result = subprocess.run(["crontab", "-l"], capture_output=True, text=True)
|
|
32
|
+
|
|
33
|
+
# Check if command succeeded
|
|
34
|
+
if result.returncode == 0:
|
|
35
|
+
current_crontab = result.stdout
|
|
36
|
+
else:
|
|
37
|
+
# If crontab is empty or doesn't exist
|
|
38
|
+
current_crontab = ""
|
|
39
|
+
|
|
40
|
+
# Check if our entry already exists (using regex to handle whitespace variations)
|
|
41
|
+
entry_pattern = (
|
|
42
|
+
re.escape(schedule.strip()) + r"\s+" + re.escape(command.strip())
|
|
43
|
+
)
|
|
44
|
+
if re.search(entry_pattern, current_crontab):
|
|
45
|
+
logger.info(f"Crontab entry for '{command}' already exists.")
|
|
46
|
+
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
# Add our entry to crontab
|
|
50
|
+
new_crontab = current_crontab
|
|
51
|
+
if new_crontab and not new_crontab.endswith("\n"):
|
|
52
|
+
new_crontab += "\n"
|
|
53
|
+
new_crontab += cron_entry + "\n"
|
|
54
|
+
|
|
55
|
+
# Write back to crontab
|
|
56
|
+
process = subprocess.Popen(["crontab", "-"], stdin=subprocess.PIPE, text=True)
|
|
57
|
+
process.communicate(input=new_crontab)
|
|
58
|
+
|
|
59
|
+
if process.returncode == 0:
|
|
60
|
+
logger.info(f"Successfully added crontab entry: '{cron_entry}'")
|
|
61
|
+
return True
|
|
62
|
+
else:
|
|
63
|
+
logger.error(f"Failed to update crontab, return code: {process.returncode}")
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Error managing crontab: {str(e)}")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def graceful_shutdown(resource_name):
|
|
72
|
+
# Log shutdown
|
|
73
|
+
|
|
74
|
+
pid = os.getpid()
|
|
75
|
+
user = os.environ.get("USER", os.environ.get("USERNAME", "unknown"))
|
|
76
|
+
hostname = socket.gethostname()
|
|
77
|
+
|
|
78
|
+
# Capture current user info for the subprocess
|
|
79
|
+
current_user = os.environ.get("USER", os.environ.get("USERNAME"))
|
|
80
|
+
|
|
81
|
+
runner_event = RunnerEvent(
|
|
82
|
+
resource=resource_name,
|
|
83
|
+
runner_type=RunnerType.RESOURCE_RUNNER,
|
|
84
|
+
event=RunnerEventEnum.SHUTDOWN,
|
|
85
|
+
pid=pid,
|
|
86
|
+
user=user,
|
|
87
|
+
hostname=hostname,
|
|
88
|
+
message=f"Graceful shutdown of {pid} by {current_user}",
|
|
89
|
+
)
|
|
90
|
+
await context.db.save(runner_event)
|
|
91
|
+
|
|
92
|
+
# Cancel all remaining tasks
|
|
93
|
+
for task in asyncio.all_tasks():
|
|
94
|
+
if task is not asyncio.current_task():
|
|
95
|
+
task.cancel()
|
|
96
|
+
|
|
97
|
+
# Give tasks time to cancel
|
|
98
|
+
await asyncio.sleep(0.5)
|
|
99
|
+
|
|
100
|
+
# Signal to the main function to exit
|
|
101
|
+
raise SystemExit(0)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
async def restart(resource_name):
|
|
105
|
+
logger.info(
|
|
106
|
+
f"Restarting node runner for resource: {resource_name} {context.config.python_paths}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
cmd = Path(context.config.project_root) / "scripts" / "old" / "check_runner.sh"
|
|
110
|
+
if not cmd.is_file():
|
|
111
|
+
raise FileNotFoundError(f"Runner restart script not found: {cmd}")
|
|
112
|
+
|
|
113
|
+
# Get current user information
|
|
114
|
+
current_uid = os.getuid()
|
|
115
|
+
current_gid = os.getgid()
|
|
116
|
+
current_pid = os.getpid()
|
|
117
|
+
logger.info(f"Current user ID: {current_uid}, group ID: {current_gid}")
|
|
118
|
+
|
|
119
|
+
# Create environment with preserved user information
|
|
120
|
+
env = os.environ.copy()
|
|
121
|
+
|
|
122
|
+
# Start a new process with the same arguments
|
|
123
|
+
|
|
124
|
+
logger.info(f"Restarting node runner for resource: {resource_name} {str(cmd)}")
|
|
125
|
+
|
|
126
|
+
result = subprocess.Popen(
|
|
127
|
+
["bash", str(cmd)], start_new_session=True, env=env
|
|
128
|
+
)
|
|
129
|
+
# Log the restart
|
|
130
|
+
message = f"user: {current_uid} group: {current_gid} pid: {current_pid} result: {result.pid}"
|
|
131
|
+
runner_event = RunnerEvent(
|
|
132
|
+
resource=resource_name,
|
|
133
|
+
runner_type=RunnerType.RESOURCE_RUNNER,
|
|
134
|
+
event=RunnerEventEnum.RESTART,
|
|
135
|
+
message=message,
|
|
136
|
+
)
|
|
137
|
+
await context.db.save(runner_event)
|
|
138
|
+
# Exit current process
|
|
139
|
+
logger.info(f"Restarted node runner for resource: {resource_name}")
|
|
140
|
+
# await graceful_shutdown(resource_name)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
async def schedule_restart(resource_name, restart_minutes):
|
|
144
|
+
"""Schedule a restart after the specified minutes"""
|
|
145
|
+
try:
|
|
146
|
+
logger.info(
|
|
147
|
+
f"Runner will restart in {restart_minutes} minutes on resource {resource_name} "
|
|
148
|
+
)
|
|
149
|
+
await asyncio.sleep(restart_minutes * 60) # Convert minutes to seconds
|
|
150
|
+
# try:
|
|
151
|
+
# if context.config.python_path:
|
|
152
|
+
# path = Path(context.config.python_path[0])
|
|
153
|
+
# command = str(path / "scripts" / "old" / "check_runner.sh") + " >> " + str(path / "cron_log.log") + " 2>&1"
|
|
154
|
+
# logger.info(f"Path: {command} ")
|
|
155
|
+
# if not ensure_crontab_entry(command):
|
|
156
|
+
# runner_entry = RunnerEvent(resource=resource_name, runner_type=RunnerType.RESOURCE_RUNNER,
|
|
157
|
+
# event=RunnerEventEnum.CRONTAB_GONE,
|
|
158
|
+
# message="Crontab entry for resource runner was not found")
|
|
159
|
+
# await context.db.save(runner_entry)
|
|
160
|
+
# except Exception as e:
|
|
161
|
+
# logger.error("Something went wrong for crontab")
|
|
162
|
+
await restart(resource_name)
|
|
163
|
+
await graceful_shutdown(resource_name)
|
|
164
|
+
except asyncio.CancelledError:
|
|
165
|
+
logger.info("Restart task was cancelled")
|
|
166
|
+
raise
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import subprocess
|
|
3
|
+
import uuid
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from odmantic import ObjectId
|
|
8
|
+
from simstack.core.context import context
|
|
9
|
+
from simstack.models.parameters import Resource
|
|
10
|
+
from simstack.models.slurm_info import SlurmInfo
|
|
11
|
+
from simstack.util.git_repository_status import get_git_status
|
|
12
|
+
from simstack.util.submit_to_watchdog import submit_to_watchdog
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("runner_utils")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_git_status_list() -> List[str]:
|
|
20
|
+
git_status_list = []
|
|
21
|
+
git_path_list = [context.config.project_root]
|
|
22
|
+
for path in git_path_list:
|
|
23
|
+
result = get_git_status(path)
|
|
24
|
+
if result["branch"]:
|
|
25
|
+
value = result["branch"] + "[" + result["short_hash"] + "]"
|
|
26
|
+
if result["up_to_date"]:
|
|
27
|
+
value += " (up-to-date)"
|
|
28
|
+
else:
|
|
29
|
+
value += " (behind " + str(result["behind"]) + " commits)"
|
|
30
|
+
git_status_list.append(value)
|
|
31
|
+
else:
|
|
32
|
+
git_status_list.append("No branch found")
|
|
33
|
+
return git_status_list
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def run_squeue_for_job(job_id: str) -> str:
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
f"squeue -j {job_id}",
|
|
39
|
+
shell=True,
|
|
40
|
+
capture_output=True,
|
|
41
|
+
text=True,
|
|
42
|
+
timeout=30,
|
|
43
|
+
)
|
|
44
|
+
return result.stdout
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_job_info(
|
|
48
|
+
job_id: str, task_id: ObjectId, resource: Resource
|
|
49
|
+
) -> SlurmInfo | None:
|
|
50
|
+
"""Get job information from SLURM queue using squeue"""
|
|
51
|
+
try:
|
|
52
|
+
stdout = run_squeue_for_job(job_id)
|
|
53
|
+
# logger.info(f"task_id: {task_id} running squeue for job {job_id}: result: {stdout}")
|
|
54
|
+
|
|
55
|
+
if not stdout or stdout.strip() == "":
|
|
56
|
+
# after a while slurm will stop returning info for jobs that are no longer running
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
lines = stdout.splitlines()
|
|
60
|
+
# logger.info(f"task_id: {task_id} slurm info for job {job_id}: {lines}")
|
|
61
|
+
if len(lines) < 2:
|
|
62
|
+
return None
|
|
63
|
+
# The first line is the header; the second line is the single info line
|
|
64
|
+
|
|
65
|
+
info_line = lines[1].strip()
|
|
66
|
+
if not info_line:
|
|
67
|
+
return None
|
|
68
|
+
# Split the single line into parts separated by whitespace
|
|
69
|
+
parts = re.split(r"\s+", info_line)
|
|
70
|
+
# logger.info(f"task_id: {task_id} slurm info for job {job_id}: {parts}")
|
|
71
|
+
# Expected default squeue columns:
|
|
72
|
+
# JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON)
|
|
73
|
+
name = parts[2] if len(parts) > 2 else ""
|
|
74
|
+
user = parts[3] if len(parts) > 3 else ""
|
|
75
|
+
code = parts[4] if len(parts) > 4 else ""
|
|
76
|
+
time_str = parts[5] if len(parts) > 5 else ""
|
|
77
|
+
nodelist_raw = parts[7] if len(parts) > 7 else ""
|
|
78
|
+
# Split nodelist on commas or whitespace, filter empties
|
|
79
|
+
nodes = [n for n in re.split(r"[,\s]+", nodelist_raw) if n]
|
|
80
|
+
|
|
81
|
+
slurm_info = SlurmInfo(
|
|
82
|
+
node_registry=task_id,
|
|
83
|
+
resource=resource,
|
|
84
|
+
job_id=job_id,
|
|
85
|
+
updated=datetime.now(),
|
|
86
|
+
name=name,
|
|
87
|
+
user=user,
|
|
88
|
+
code=code,
|
|
89
|
+
time=time_str,
|
|
90
|
+
nodes=nodes,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return slurm_info
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.exception(f"Error getting job info for {job_id}: {str(e)}")
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def clean_slurm_info(resource: Resource, user: str | None = None) -> None:
|
|
100
|
+
"""Clean up old slurm info entries"""
|
|
101
|
+
try:
|
|
102
|
+
squeue_cmd = "squeue"
|
|
103
|
+
if user:
|
|
104
|
+
squeue_cmd += f" -u {user}"
|
|
105
|
+
|
|
106
|
+
if context.config.docker:
|
|
107
|
+
watchdog_id = f"slurm_{uuid.uuid4()}"
|
|
108
|
+
queue_dir = context.config.workdir / "queue"
|
|
109
|
+
result = submit_to_watchdog(squeue_cmd, watchdog_id, queue_dir=queue_dir)
|
|
110
|
+
else:
|
|
111
|
+
result = subprocess.run(
|
|
112
|
+
squeue_cmd,
|
|
113
|
+
shell=True,
|
|
114
|
+
capture_output=True,
|
|
115
|
+
text=True,
|
|
116
|
+
timeout=30,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if result.returncode == 0:
|
|
120
|
+
active_job_ids = set()
|
|
121
|
+
for line in result.stdout.splitlines():
|
|
122
|
+
parts = line.split()
|
|
123
|
+
if not parts or parts[0] == "JOBID":
|
|
124
|
+
continue
|
|
125
|
+
active_job_ids.add(parts[0])
|
|
126
|
+
|
|
127
|
+
# Find all SLURM info entries for this resource
|
|
128
|
+
# the user id is truncated on saving
|
|
129
|
+
running_jobs = await context.db.find(
|
|
130
|
+
SlurmInfo, SlurmInfo.resource.value == resource.value
|
|
131
|
+
)
|
|
132
|
+
# logger.info(f"Found {running_jobs} slurm info entries for {resource}")
|
|
133
|
+
# logger.info(f"Active job IDs: {active_job_ids} Slurm info IDs: {[job.job_id for job in running_jobs]}")
|
|
134
|
+
# logger.info(f"User: {user} resource: {resource} ")
|
|
135
|
+
|
|
136
|
+
# Delete entries for jobs that are no longer running
|
|
137
|
+
for job in running_jobs:
|
|
138
|
+
if job.job_id not in active_job_ids:
|
|
139
|
+
await context.db.delete(job)
|
|
140
|
+
logger.info(f"Deleted SLURM info for completed job {job.job_id}")
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.exception(f"Error cleaning slurm info for {resource}: {str(e)}")
|