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,222 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from odmantic import AIOEngine
|
|
6
|
+
|
|
7
|
+
from simstack.models import NodeRegistry
|
|
8
|
+
from simstack.models.parameters import Parameters, Resource, SlurmParameters
|
|
9
|
+
from simstack.models.resource_assignment import (
|
|
10
|
+
ResourceAssignmentRule,
|
|
11
|
+
SlurmParametersPatch,
|
|
12
|
+
)
|
|
13
|
+
from simstack.util.db import Database
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("resource_assignment")
|
|
16
|
+
|
|
17
|
+
SLURM_QUEUE_NAME = "slurm-queue"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ResourceAssignmentResolution:
|
|
22
|
+
parameters: Parameters
|
|
23
|
+
normalized_call_path: str
|
|
24
|
+
matched_rule: Optional[ResourceAssignmentRule] = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def normalize_call_path(call_path: Optional[str]) -> str:
|
|
28
|
+
return (call_path or "").strip().lstrip(".")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _normalize_queue(queue: Optional[str]) -> str:
|
|
32
|
+
normalized = (queue or "").strip()
|
|
33
|
+
return normalized or "default"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_slurm_queue(queue: Optional[str]) -> bool:
|
|
37
|
+
return _normalize_queue(queue).lower() == SLURM_QUEUE_NAME
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _clone_parameters(parameters: Optional[Parameters]) -> Parameters:
|
|
41
|
+
if isinstance(parameters, Parameters):
|
|
42
|
+
return parameters.model_copy(deep=True)
|
|
43
|
+
return Parameters()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _merge_slurm_patch(
|
|
47
|
+
patch: Optional[SlurmParametersPatch],
|
|
48
|
+
) -> SlurmParameters:
|
|
49
|
+
if patch is None:
|
|
50
|
+
return SlurmParameters()
|
|
51
|
+
return SlurmParameters(**patch.model_dump(exclude_none=True))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def empty_slurm_parameters() -> SlurmParameters:
|
|
55
|
+
cleared_values: dict[str, object] = {}
|
|
56
|
+
for field_name, field_info in SlurmParameters.model_fields.items():
|
|
57
|
+
if field_info.default_factory is not None:
|
|
58
|
+
cleared_values[field_name] = field_info.default_factory()
|
|
59
|
+
else:
|
|
60
|
+
cleared_values[field_name] = None
|
|
61
|
+
return SlurmParameters.model_validate(cleared_values)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _apply_assignment_patch(
|
|
65
|
+
base_parameters: Parameters,
|
|
66
|
+
rule: Optional[ResourceAssignmentRule],
|
|
67
|
+
) -> Parameters:
|
|
68
|
+
effective = _clone_parameters(base_parameters)
|
|
69
|
+
if rule is None:
|
|
70
|
+
return effective
|
|
71
|
+
|
|
72
|
+
if rule.resource_str:
|
|
73
|
+
effective.resource = Resource(value=rule.resource_str)
|
|
74
|
+
if rule.queue is not None:
|
|
75
|
+
effective.queue = _normalize_queue(rule.queue)
|
|
76
|
+
if rule.slurm_parameters_patch:
|
|
77
|
+
effective.slurm_parameters = _merge_slurm_patch(
|
|
78
|
+
SlurmParametersPatch.model_validate(rule.slurm_parameters_patch),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return effective
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def normalize_and_validate_effective_parameters(
|
|
85
|
+
parameters: Optional[Parameters],
|
|
86
|
+
) -> None:
|
|
87
|
+
if parameters is None:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if not _is_slurm_queue(getattr(parameters, "queue", None)):
|
|
91
|
+
parameters.slurm_parameters = empty_slurm_parameters()
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
slurm_parameters = getattr(parameters, "slurm_parameters", None)
|
|
95
|
+
if slurm_parameters is None:
|
|
96
|
+
raise ValueError('Slurm queue requires "slurm_parameters".')
|
|
97
|
+
|
|
98
|
+
fields_set: set[str] = getattr(slurm_parameters, "model_fields_set", set())
|
|
99
|
+
has_nodes = "nodes" in fields_set and slurm_parameters.nodes is not None
|
|
100
|
+
has_tasks = "tasks" in fields_set and slurm_parameters.tasks is not None
|
|
101
|
+
has_tasks_per_node = (
|
|
102
|
+
"tasks_per_node" in fields_set and slurm_parameters.tasks_per_node is not None
|
|
103
|
+
)
|
|
104
|
+
uses_default_nodes = (
|
|
105
|
+
not has_nodes
|
|
106
|
+
and not has_tasks
|
|
107
|
+
and not has_tasks_per_node
|
|
108
|
+
and slurm_parameters.nodes is not None
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if uses_default_nodes:
|
|
112
|
+
has_nodes = True
|
|
113
|
+
elif not has_nodes:
|
|
114
|
+
slurm_parameters.nodes = None
|
|
115
|
+
if not has_tasks:
|
|
116
|
+
slurm_parameters.tasks = None
|
|
117
|
+
if not has_tasks_per_node:
|
|
118
|
+
slurm_parameters.tasks_per_node = None
|
|
119
|
+
|
|
120
|
+
if has_tasks and has_tasks_per_node:
|
|
121
|
+
raise ValueError(
|
|
122
|
+
'Slurm parameters conflict: use either "tasks" or "tasks_per_node".'
|
|
123
|
+
)
|
|
124
|
+
if not has_nodes and not has_tasks:
|
|
125
|
+
raise ValueError('Slurm requires at least one of "nodes" or "tasks".')
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _select_matching_rule(
|
|
129
|
+
normalized_call_path: str,
|
|
130
|
+
rules: list[ResourceAssignmentRule],
|
|
131
|
+
) -> Optional[ResourceAssignmentRule]:
|
|
132
|
+
enabled_rules = [rule for rule in rules if getattr(rule, "enabled", True)]
|
|
133
|
+
matching_rules = [
|
|
134
|
+
rule
|
|
135
|
+
for rule in enabled_rules
|
|
136
|
+
if ResourceAssignmentRule.matches_call_path(
|
|
137
|
+
rule.regex_pattern, normalized_call_path
|
|
138
|
+
)
|
|
139
|
+
]
|
|
140
|
+
if not matching_rules:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
highest_score = max(
|
|
144
|
+
ResourceAssignmentRule.pattern_specificity_score(rule.regex_pattern)
|
|
145
|
+
for rule in matching_rules
|
|
146
|
+
)
|
|
147
|
+
top_rules = [
|
|
148
|
+
rule
|
|
149
|
+
for rule in matching_rules
|
|
150
|
+
if ResourceAssignmentRule.pattern_specificity_score(rule.regex_pattern)
|
|
151
|
+
== highest_score
|
|
152
|
+
]
|
|
153
|
+
if len(top_rules) > 1:
|
|
154
|
+
rule_names = ", ".join(sorted(rule.name for rule in top_rules))
|
|
155
|
+
raise ValueError(
|
|
156
|
+
"Ambiguous resource assignment: "
|
|
157
|
+
f"multiple equally specific rules matched call_path '{normalized_call_path}': {rule_names}"
|
|
158
|
+
)
|
|
159
|
+
return top_rules[0]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def resolve_resource_assignment(
|
|
163
|
+
db: Database,
|
|
164
|
+
*,
|
|
165
|
+
call_path: Optional[str],
|
|
166
|
+
base_parameters: Optional[Parameters],
|
|
167
|
+
parent_parameters: Optional[Parameters] = None,
|
|
168
|
+
) -> ResourceAssignmentResolution:
|
|
169
|
+
normalized_call_path = normalize_call_path(call_path)
|
|
170
|
+
effective_base = _clone_parameters(base_parameters)
|
|
171
|
+
|
|
172
|
+
if not normalized_call_path:
|
|
173
|
+
normalize_and_validate_effective_parameters(effective_base)
|
|
174
|
+
return ResourceAssignmentResolution(
|
|
175
|
+
parameters=effective_base,
|
|
176
|
+
normalized_call_path=normalized_call_path,
|
|
177
|
+
matched_rule=None,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
rules = await db.find(ResourceAssignmentRule)
|
|
181
|
+
matched_rule = _select_matching_rule(normalized_call_path, list(rules))
|
|
182
|
+
effective_parameters = _apply_assignment_patch(effective_base, matched_rule)
|
|
183
|
+
normalize_and_validate_effective_parameters(effective_parameters)
|
|
184
|
+
|
|
185
|
+
if matched_rule is not None:
|
|
186
|
+
logger.info(
|
|
187
|
+
"Applied resource assignment rule '%s' to call_path '%s'",
|
|
188
|
+
matched_rule.name,
|
|
189
|
+
normalized_call_path,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return ResourceAssignmentResolution(
|
|
193
|
+
parameters=effective_parameters,
|
|
194
|
+
normalized_call_path=normalized_call_path,
|
|
195
|
+
matched_rule=matched_rule,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
async def apply_resource_assignment_to_node_registry(
|
|
200
|
+
db: Database,
|
|
201
|
+
node_registry: NodeRegistry,
|
|
202
|
+
*,
|
|
203
|
+
parent_parameters: Optional[Parameters] = None,
|
|
204
|
+
) -> ResourceAssignmentResolution:
|
|
205
|
+
resolution = await resolve_resource_assignment(
|
|
206
|
+
db,
|
|
207
|
+
call_path=getattr(node_registry, "call_path", None),
|
|
208
|
+
base_parameters=getattr(node_registry, "parameters", None),
|
|
209
|
+
parent_parameters=parent_parameters,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
node_registry.parameters = resolution.parameters
|
|
213
|
+
if resolution.matched_rule is None:
|
|
214
|
+
node_registry.assignment_rule_id = None
|
|
215
|
+
node_registry.assignment_rule_name = None
|
|
216
|
+
node_registry.assignment_pattern = None
|
|
217
|
+
else:
|
|
218
|
+
node_registry.assignment_rule_id = str(resolution.matched_rule.id)
|
|
219
|
+
node_registry.assignment_rule_name = resolution.matched_rule.name
|
|
220
|
+
node_registry.assignment_pattern = resolution.matched_rule.regex_pattern
|
|
221
|
+
|
|
222
|
+
return resolution
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List
|
|
3
|
+
logger = logging.getLogger("resources")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AllowedResources:
|
|
7
|
+
"""
|
|
8
|
+
Singleton class that holds a list of allowed resource strings.
|
|
9
|
+
|
|
10
|
+
This class is problematic because the @node decorator is called before the config is read.
|
|
11
|
+
@node may contain Parameters which set default values for resources.
|
|
12
|
+
The solution is to create the AllowedResources singleton class here, and set the resources from
|
|
13
|
+
context.initialize. Before context.initialize is called, any resource is allowed.
|
|
14
|
+
|
|
15
|
+
However, resources are validated on read, so before any node is executed, the resources must be set.
|
|
16
|
+
AllowedResources can be set only once.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_instance = None
|
|
20
|
+
_resources: List[str] = []
|
|
21
|
+
|
|
22
|
+
def __new__(cls):
|
|
23
|
+
if cls._instance is None:
|
|
24
|
+
cls._instance = super(AllowedResources, cls).__new__(cls)
|
|
25
|
+
return cls._instance
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
# Only initialize once
|
|
29
|
+
self._initialized = False
|
|
30
|
+
|
|
31
|
+
def set_resources(self, resources: List[str]) -> None:
|
|
32
|
+
"""Set the list of allowed resources."""
|
|
33
|
+
if self._initialized and resources != self._resources:
|
|
34
|
+
raise RuntimeError("Resources can only be set once")
|
|
35
|
+
self._initialized = True
|
|
36
|
+
self._resources = resources.copy() if resources else []
|
|
37
|
+
|
|
38
|
+
def get_resources(self) -> List[str]:
|
|
39
|
+
"""Get the list of allowed resources."""
|
|
40
|
+
return self._resources.copy()
|
|
41
|
+
|
|
42
|
+
def add_resource(self, resource: str) -> None:
|
|
43
|
+
"""Add a single resource to the list."""
|
|
44
|
+
if resource not in self._resources:
|
|
45
|
+
self._resources.append(resource)
|
|
46
|
+
|
|
47
|
+
def remove_resource(self, resource: str) -> None:
|
|
48
|
+
if resource == "self":
|
|
49
|
+
raise ValueError("Cannot remove 'self' resource")
|
|
50
|
+
"""Remove a resource from the list."""
|
|
51
|
+
if resource in self._resources:
|
|
52
|
+
self._resources.remove(resource)
|
|
53
|
+
|
|
54
|
+
def clear_resources(self) -> None:
|
|
55
|
+
"""Clear all resources from the list."""
|
|
56
|
+
self._resources.clear()
|
|
57
|
+
self._initialized = False
|
|
58
|
+
|
|
59
|
+
def has_resource(self, resource: str) -> bool:
|
|
60
|
+
"""Check if a resource exists in the list."""
|
|
61
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
62
|
+
return resource in self._resources
|
|
63
|
+
else:
|
|
64
|
+
return True # before this is initialized, any resource is allowed
|
|
65
|
+
|
|
66
|
+
def __len__(self) -> int:
|
|
67
|
+
"""Return the number of resources."""
|
|
68
|
+
return len(self._resources)
|
|
69
|
+
|
|
70
|
+
def __iter__(self):
|
|
71
|
+
"""Make the class iterable."""
|
|
72
|
+
return iter(self._resources)
|
|
73
|
+
|
|
74
|
+
def __contains__(self, resource: str) -> bool:
|
|
75
|
+
"""Support 'in' operator."""
|
|
76
|
+
return resource in self._resources
|
|
77
|
+
|
|
78
|
+
def __str__(self) -> str:
|
|
79
|
+
"""String representation."""
|
|
80
|
+
return f"AllowedResources({self._resources})"
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
"""String representation."""
|
|
84
|
+
return f"AllowedResources({self._resources!r})"
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def initialized(self):
|
|
88
|
+
return hasattr(self, "_initialized")
|
|
89
|
+
|
|
90
|
+
allowed_resources = AllowedResources()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import List, Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RouteTable:
|
|
5
|
+
_instance = None
|
|
6
|
+
|
|
7
|
+
def __init__(self):
|
|
8
|
+
if RouteTable._instance is not None:
|
|
9
|
+
raise RuntimeError("Use get_instance() instead")
|
|
10
|
+
self.targets = {}
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def get_instance(cls) -> 'RouteTable':
|
|
14
|
+
if cls._instance is None:
|
|
15
|
+
cls._instance = cls()
|
|
16
|
+
return cls._instance
|
|
17
|
+
|
|
18
|
+
def add_route_set(self, source: str, targets: List[str]) -> None:
|
|
19
|
+
self.targets[source] = targets
|
|
20
|
+
|
|
21
|
+
def clear_routes(self) -> None:
|
|
22
|
+
self.targets.clear()
|
|
23
|
+
|
|
24
|
+
route_table = RouteTable.get_instance()
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from simstack.models import NodeRegistry
|
|
2
|
+
from simstack.core.definitions import TaskStatus
|
|
3
|
+
from simstack.core.context import context
|
|
4
|
+
import asyncio
|
|
5
|
+
import platform
|
|
6
|
+
import subprocess
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import locale
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("DockerRunner")
|
|
12
|
+
|
|
13
|
+
async def run_docker(registry_entry: NodeRegistry) -> bool:
|
|
14
|
+
resource = context.config.resource
|
|
15
|
+
parameters = registry_entry.parameters
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
program_config = context.resource_config.get_program(registry_entry.name)
|
|
19
|
+
image= program_config.get("docker_image", None)
|
|
20
|
+
if image is None:
|
|
21
|
+
logger.error(f"Docker image not found for task_id={registry_entry.id}")
|
|
22
|
+
registry_entry.status = TaskStatus.FAILED
|
|
23
|
+
await context.db.save(registry_entry)
|
|
24
|
+
return False
|
|
25
|
+
docker_cmd = program_config.get("docker_cmd", None)
|
|
26
|
+
if docker_cmd is None:
|
|
27
|
+
docker_cmd = "docker"
|
|
28
|
+
|
|
29
|
+
if docker_cmd == "apptainer":
|
|
30
|
+
if not image.endswith(".sif") and not image.startswith("docker://"):
|
|
31
|
+
image += ".sif"
|
|
32
|
+
|
|
33
|
+
workdir = context.config.workdir
|
|
34
|
+
host_simstack_toml = context.config.project_root / "simstack.toml"
|
|
35
|
+
|
|
36
|
+
if docker_cmd == "docker":
|
|
37
|
+
cmd = [
|
|
38
|
+
"docker", "run",
|
|
39
|
+
"-e", f"SIMSTACK_DB_DATABASE={context.config.db_name}",
|
|
40
|
+
"-e", f"SIMSTACK_DB_TEST_DATABASE={context.config.db_name}",
|
|
41
|
+
"-e", f"SIMSTACK_DB_CONNECTION_STRING={context.config.connection_string}",
|
|
42
|
+
"-v", f"{workdir}:/root/simstack",
|
|
43
|
+
"-v", f"{host_simstack_toml}:/app/simstack.toml",
|
|
44
|
+
image,
|
|
45
|
+
"--node-id", str(registry_entry.id), "--resource", str(resource), "--project-root", "/app"
|
|
46
|
+
]
|
|
47
|
+
elif docker_cmd == "apptainer":
|
|
48
|
+
cmd = [
|
|
49
|
+
"apptainer", "run",
|
|
50
|
+
"--env", f"SIMSTACK_DB_DATABASE={context.config.db_name}",
|
|
51
|
+
"--env", f"SIMSTACK_DB_TEST_DATABASE={context.config.db_name}",
|
|
52
|
+
"--env", f"SIMSTACK_DB_CONNECTION_STRING={context.config.connection_string}",
|
|
53
|
+
"--bind", f"{workdir}:/root/simstack",
|
|
54
|
+
"--bind", f"{host_simstack_toml}:/app/simstack.toml",
|
|
55
|
+
image,
|
|
56
|
+
"--node-id", str(registry_entry.id),
|
|
57
|
+
"--resource", str(resource),
|
|
58
|
+
"--project-root", "/app",
|
|
59
|
+
]
|
|
60
|
+
else:
|
|
61
|
+
logger.error(f"Unsupported command {docker_cmd} for task_id={registry_entry.id}")
|
|
62
|
+
registry_entry.status = TaskStatus.FAILED
|
|
63
|
+
await context.db.save(registry_entry)
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
# Use platform specific flags to ensure the process survives if runner is killed
|
|
67
|
+
creationflags = 0
|
|
68
|
+
if platform.system() == "Windows":
|
|
69
|
+
creationflags = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
process = await asyncio.create_subprocess_exec(
|
|
73
|
+
*cmd,
|
|
74
|
+
stdout=asyncio.subprocess.PIPE,
|
|
75
|
+
stderr=asyncio.subprocess.PIPE,
|
|
76
|
+
creationflags=creationflags,
|
|
77
|
+
start_new_session=True if platform.system() != "Windows" else False
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
stdout_b, stderr_b = await process.communicate()
|
|
81
|
+
|
|
82
|
+
enc = locale.getpreferredencoding(False) or "utf-8"
|
|
83
|
+
stdout = (stdout_b or b"").decode(enc, errors="replace").strip()
|
|
84
|
+
stderr = (stderr_b or b"").decode(enc, errors="replace").strip()
|
|
85
|
+
|
|
86
|
+
if process.returncode != 0:
|
|
87
|
+
logger.error(
|
|
88
|
+
"docker run failed for task_id=%s rc=%s stderr=%s stdout=%s cmd=%s",
|
|
89
|
+
registry_entry.id, process.returncode, stderr, stdout, cmd
|
|
90
|
+
)
|
|
91
|
+
registry_entry.status = TaskStatus.FAILED
|
|
92
|
+
await context.db.save(registry_entry)
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
if stdout:
|
|
96
|
+
# For `docker run` without -d, stdout is the actual output of the command
|
|
97
|
+
logger.info("Docker container output for task_id=%s: %s", registry_entry.id, stdout)
|
|
98
|
+
else:
|
|
99
|
+
logger.info("Docker container spawned for task_id=%s", registry_entry.id)
|
|
100
|
+
if stderr:
|
|
101
|
+
# Some Docker setups warn on stderr even on success
|
|
102
|
+
logger.warning("docker run stderr for task_id=%s: %s", registry_entry.id, stderr)
|
|
103
|
+
|
|
104
|
+
return True
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.exception(f"fatal error in running docker task_id: {registry_entry.id} {str(e)}")
|
|
107
|
+
registry_entry.status = TaskStatus.FAILED
|
|
108
|
+
await context.db.save(registry_entry)
|
|
109
|
+
return False
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from simstack.core.context import context
|
|
8
|
+
from simstack.core.definitions import TaskStatus
|
|
9
|
+
from simstack.core.node import node_from_database
|
|
10
|
+
from simstack.core.services.node_execution_service import run_node_from_registry
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("Run Node")
|
|
13
|
+
|
|
14
|
+
async def run_node_from_id(node_id: str, resource_str: str, project_root: str = None):
|
|
15
|
+
"""Run a single node by its ID from the database"""
|
|
16
|
+
logger.info(f"Initializing node run: node_id={node_id}, resource={resource_str}, project_root={project_root}")
|
|
17
|
+
try:
|
|
18
|
+
await context.initialize(resource=resource_str, project_root=project_root)
|
|
19
|
+
except Exception as e:
|
|
20
|
+
logger.error(f"Failed to initialize context: {str(e)}", exc_info=True)
|
|
21
|
+
return False
|
|
22
|
+
registry_entry = None
|
|
23
|
+
try:
|
|
24
|
+
registry_entry = await context.db.load_task_by_id(node_id)
|
|
25
|
+
if not registry_entry:
|
|
26
|
+
logger.error(f"Node with ID {node_id} not found in the database")
|
|
27
|
+
return False
|
|
28
|
+
return await run_node_from_registry(registry_entry)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
logger.exception(f"Error running node task_id: {node_id}: {str(e)}")
|
|
31
|
+
if registry_entry:
|
|
32
|
+
registry_entry.status = TaskStatus.FAILED
|
|
33
|
+
await context.db.save(registry_entry)
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
def run_node_main():
|
|
37
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', stream=sys.stdout)
|
|
38
|
+
logger.info("Starting run_node_main")
|
|
39
|
+
parser = argparse.ArgumentParser(description="Run nodes for a specific resource")
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--node-id",
|
|
42
|
+
type=str,
|
|
43
|
+
help="Specific node ID to run (overrides resource-based polling)",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--resource",
|
|
48
|
+
default="local",
|
|
49
|
+
nargs="?",
|
|
50
|
+
type=str,
|
|
51
|
+
help="resource to load",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--project-root",
|
|
56
|
+
default=None,
|
|
57
|
+
nargs="?",
|
|
58
|
+
type=str,
|
|
59
|
+
help="project root directory",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if args.node_id:
|
|
66
|
+
# Run a specific node once
|
|
67
|
+
asyncio.run(run_node_from_id(args.node_id, args.resource, args.project_root))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
run_node_main()
|
simstack/core/runner.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from simstack.core.definitions import DBType
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 11):
|
|
10
|
+
import tomllib # Python 3.11+
|
|
11
|
+
else:
|
|
12
|
+
import tomli as tomllib # type: ignore[import-not-found]
|
|
13
|
+
|
|
14
|
+
from simstack.core.context import context
|
|
15
|
+
from simstack.models.resource_definition import ResourceDefinition
|
|
16
|
+
from simstack.tables.node_table import make_node_table
|
|
17
|
+
from simstack.tables.model_table import make_model_table
|
|
18
|
+
from simstack.core.services.runner_manager import RunnerManager
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("NodeRunner")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def initialize_default_resource() -> ResourceDefinition | None:
|
|
24
|
+
"""
|
|
25
|
+
Checks if the current resource is the default one.
|
|
26
|
+
If so, syncs the node and model tables based on config.toml.
|
|
27
|
+
"""
|
|
28
|
+
resource_def = await context.db.find_one(
|
|
29
|
+
ResourceDefinition,
|
|
30
|
+
ResourceDefinition.resource_str == str(context.config.resource),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if resource_def is None:
|
|
34
|
+
logger.warning(
|
|
35
|
+
"No ResourceDefinition found for '%s'; skipping default-resource initialization.",
|
|
36
|
+
str(context.config.resource),
|
|
37
|
+
)
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
if resource_def.is_default:
|
|
41
|
+
config_path = context.config.project_root / "config.toml"
|
|
42
|
+
if not config_path.exists():
|
|
43
|
+
logger.warning(f"Default resource detected, but {config_path} not found.")
|
|
44
|
+
return resource_def
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
with open(config_path, "rb") as f:
|
|
48
|
+
config_data = tomllib.load(f)
|
|
49
|
+
|
|
50
|
+
active_dirs = config_data.get("active_dirs", [])
|
|
51
|
+
if not active_dirs:
|
|
52
|
+
logger.info("No active_dirs found in config.toml.")
|
|
53
|
+
return resource_def
|
|
54
|
+
|
|
55
|
+
logger.info(f"Default resource: initializing tables for {active_dirs}")
|
|
56
|
+
await make_model_table(context.db, dirs=active_dirs)
|
|
57
|
+
await make_node_table(context.db, dirs=active_dirs)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logger.error(f"Failed to initialize default resource tables: {e}")
|
|
61
|
+
|
|
62
|
+
return resource_def
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def async_main(args: argparse.Namespace) -> None:
|
|
66
|
+
"""Async entry point"""
|
|
67
|
+
if args.connection_string == "none" or args.db_name == "none":
|
|
68
|
+
await context.initialize(resource=args.resource, config_file=args.config)
|
|
69
|
+
else:
|
|
70
|
+
await context.initialize(
|
|
71
|
+
resource=args.resource,
|
|
72
|
+
db_name=args.db_name,
|
|
73
|
+
connection_string=args.connection_string,
|
|
74
|
+
db_type=DBType.MONGODB,
|
|
75
|
+
config_file=args.config,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Initialize tables if this is the default resource
|
|
79
|
+
resource_def = await initialize_default_resource()
|
|
80
|
+
is_default_resource = bool(resource_def and resource_def.is_default)
|
|
81
|
+
|
|
82
|
+
if args.resource:
|
|
83
|
+
logger.info(f"Setting resource for runner to {args.resource}")
|
|
84
|
+
runner_manager = RunnerManager(
|
|
85
|
+
context.config.resource,
|
|
86
|
+
detach=args.detach,
|
|
87
|
+
no_pull=args.no_pull,
|
|
88
|
+
is_default=is_default_resource,
|
|
89
|
+
with_file_transfer=args.with_file_transfer,
|
|
90
|
+
)
|
|
91
|
+
await runner_manager.run_nodes_for_resource(
|
|
92
|
+
args.polling_interval, 10, timeout=args.timeout
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def runner_main() -> None:
|
|
97
|
+
parser = argparse.ArgumentParser(description="Run nodes for a specific resource")
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"--config",
|
|
100
|
+
type=str,
|
|
101
|
+
# default="config.toml",
|
|
102
|
+
help="Path to the configuration file",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"--resource",
|
|
107
|
+
type=str,
|
|
108
|
+
default="local",
|
|
109
|
+
help="Resource name to process tasks for",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
"--db-name",
|
|
114
|
+
type=str,
|
|
115
|
+
default="none",
|
|
116
|
+
help="Specify a non-standard database",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
"--connection-string",
|
|
121
|
+
type=str,
|
|
122
|
+
default="none",
|
|
123
|
+
help="Specify a non-standard connection string",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--polling-interval",
|
|
128
|
+
type=int,
|
|
129
|
+
default=20,
|
|
130
|
+
help="Interval in seconds between polling for new tasks",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--detach",
|
|
135
|
+
type=lambda x: (str(x).lower() not in ["false", "0", "no"]),
|
|
136
|
+
default=True,
|
|
137
|
+
help="If true (default), run nodes in an external process. Set to 'false' to run inline.",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
"--timeout",
|
|
142
|
+
type=int,
|
|
143
|
+
default=None,
|
|
144
|
+
help="Timeout in minutes after which the runner will terminate",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--no-pull",
|
|
149
|
+
action="store_true",
|
|
150
|
+
default=False,
|
|
151
|
+
help="If true, do not pull from git (GitUvUpdateService will not be started)",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--with-file-transfer",
|
|
156
|
+
type=lambda x: (str(x).lower() not in ["false", "0", "no"]),
|
|
157
|
+
default=True,
|
|
158
|
+
help="If true (default), start the FileTransferService.",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
args = parser.parse_args()
|
|
162
|
+
# Run the async main function
|
|
163
|
+
asyncio.run(async_main(args))
|
|
164
|
+
pid = os.getpid()
|
|
165
|
+
logger.info(f"runner with pid {pid} shutting down normally")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
runner_main()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from simstack.core.services.base_service import BaseService, RestartService
|
|
2
|
+
from simstack.core.services.git_uv_update_service import GitUvUpdateService
|
|
3
|
+
from simstack.core.services.node_execution_service import NodeExecutionService
|
|
4
|
+
from simstack.core.services.runner_status_service import RunnerStatusService
|
|
5
|
+
from simstack.core.services.resource_branch_monitor_service import ResourceBranchMonitorService
|
|
6
|
+
from simstack.core.services.runner_cleanup_service import RunnerCleanupService
|
|
7
|
+
from simstack.core.services.slurm_status_service import SlurmStatusService
|
|
8
|
+
from simstack.core.services.git_restart_service import GitRestartService
|
|
9
|
+
from simstack.core.services.stop_check_service import StopCheckService
|
|
10
|
+
from simstack.core.services.timeout_restart_service import TimeoutRestartService
|
|
11
|
+
from simstack.core.services.runner_manager import RunnerManager
|