parsl 2024.3.18__py3-none-any.whl → 2025.1.13__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.
- parsl/__init__.py +9 -10
- parsl/addresses.py +26 -6
- parsl/app/app.py +7 -8
- parsl/app/bash.py +15 -8
- parsl/app/errors.py +10 -13
- parsl/app/futures.py +8 -10
- parsl/app/python.py +2 -1
- parsl/benchmark/perf.py +2 -1
- parsl/concurrent/__init__.py +2 -2
- parsl/config.py +53 -10
- parsl/configs/ASPIRE1.py +6 -5
- parsl/configs/Azure.py +9 -8
- parsl/configs/bridges.py +6 -4
- parsl/configs/cc_in2p3.py +3 -3
- parsl/configs/ec2.py +3 -1
- parsl/configs/expanse.py +4 -3
- parsl/configs/frontera.py +3 -4
- parsl/configs/htex_local.py +3 -4
- parsl/configs/illinoiscluster.py +3 -1
- parsl/configs/improv.py +34 -0
- parsl/configs/kubernetes.py +4 -3
- parsl/configs/local_threads.py +5 -1
- parsl/configs/midway.py +5 -3
- parsl/configs/osg.py +4 -2
- parsl/configs/polaris.py +4 -2
- parsl/configs/stampede2.py +6 -5
- parsl/configs/summit.py +3 -3
- parsl/configs/toss3_llnl.py +4 -3
- parsl/configs/vineex_local.py +6 -4
- parsl/configs/wqex_local.py +5 -3
- parsl/curvezmq.py +4 -0
- parsl/data_provider/data_manager.py +4 -3
- parsl/data_provider/file_noop.py +1 -2
- parsl/data_provider/files.py +3 -3
- parsl/data_provider/ftp.py +1 -3
- parsl/data_provider/globus.py +7 -6
- parsl/data_provider/http.py +2 -2
- parsl/data_provider/rsync.py +1 -1
- parsl/data_provider/staging.py +2 -2
- parsl/data_provider/zip.py +135 -0
- parsl/dataflow/dependency_resolvers.py +115 -0
- parsl/dataflow/dflow.py +259 -223
- parsl/dataflow/errors.py +3 -5
- parsl/dataflow/futures.py +27 -14
- parsl/dataflow/memoization.py +5 -5
- parsl/dataflow/rundirs.py +5 -6
- parsl/dataflow/taskrecord.py +4 -5
- parsl/executors/__init__.py +4 -2
- parsl/executors/base.py +45 -15
- parsl/executors/errors.py +13 -0
- parsl/executors/execute_task.py +37 -0
- parsl/executors/flux/execute_parsl_task.py +3 -3
- parsl/executors/flux/executor.py +18 -19
- parsl/executors/flux/flux_instance_manager.py +26 -27
- parsl/executors/high_throughput/errors.py +43 -3
- parsl/executors/high_throughput/executor.py +307 -285
- parsl/executors/high_throughput/interchange.py +137 -168
- parsl/executors/high_throughput/manager_record.py +4 -0
- parsl/executors/high_throughput/manager_selector.py +55 -0
- parsl/executors/high_throughput/monitoring_info.py +2 -1
- parsl/executors/high_throughput/mpi_executor.py +113 -0
- parsl/executors/high_throughput/mpi_prefix_composer.py +10 -11
- parsl/executors/high_throughput/mpi_resource_management.py +6 -17
- parsl/executors/high_throughput/probe.py +9 -7
- parsl/executors/high_throughput/process_worker_pool.py +77 -75
- parsl/executors/high_throughput/zmq_pipes.py +81 -23
- parsl/executors/radical/executor.py +130 -79
- parsl/executors/radical/rpex_resources.py +17 -15
- parsl/executors/radical/rpex_worker.py +4 -3
- parsl/executors/status_handling.py +157 -51
- parsl/executors/taskvine/__init__.py +1 -1
- parsl/executors/taskvine/errors.py +1 -1
- parsl/executors/taskvine/exec_parsl_function.py +2 -2
- parsl/executors/taskvine/executor.py +38 -55
- parsl/executors/taskvine/factory.py +1 -1
- parsl/executors/taskvine/factory_config.py +1 -1
- parsl/executors/taskvine/manager.py +17 -13
- parsl/executors/taskvine/manager_config.py +7 -2
- parsl/executors/threads.py +6 -6
- parsl/executors/workqueue/errors.py +1 -1
- parsl/executors/workqueue/exec_parsl_function.py +6 -5
- parsl/executors/workqueue/executor.py +64 -63
- parsl/executors/workqueue/parsl_coprocess.py +1 -1
- parsl/jobs/error_handlers.py +2 -2
- parsl/jobs/job_status_poller.py +28 -112
- parsl/jobs/states.py +7 -2
- parsl/jobs/strategy.py +43 -31
- parsl/launchers/__init__.py +12 -3
- parsl/launchers/errors.py +1 -1
- parsl/launchers/launchers.py +0 -6
- parsl/log_utils.py +1 -2
- parsl/monitoring/db_manager.py +55 -93
- parsl/monitoring/errors.py +6 -0
- parsl/monitoring/monitoring.py +85 -311
- parsl/monitoring/queries/pandas.py +1 -2
- parsl/monitoring/radios/base.py +13 -0
- parsl/monitoring/radios/filesystem.py +52 -0
- parsl/monitoring/radios/htex.py +57 -0
- parsl/monitoring/radios/multiprocessing.py +17 -0
- parsl/monitoring/radios/udp.py +56 -0
- parsl/monitoring/radios/zmq.py +17 -0
- parsl/monitoring/remote.py +33 -37
- parsl/monitoring/router.py +212 -0
- parsl/monitoring/types.py +5 -6
- parsl/monitoring/visualization/app.py +4 -2
- parsl/monitoring/visualization/models.py +0 -1
- parsl/monitoring/visualization/plots/default/workflow_plots.py +8 -4
- parsl/monitoring/visualization/plots/default/workflow_resource_plots.py +1 -0
- parsl/monitoring/visualization/utils.py +0 -1
- parsl/monitoring/visualization/views.py +16 -9
- parsl/multiprocessing.py +0 -1
- parsl/process_loggers.py +1 -2
- parsl/providers/__init__.py +8 -17
- parsl/providers/aws/aws.py +2 -3
- parsl/providers/azure/azure.py +4 -5
- parsl/providers/base.py +2 -18
- parsl/providers/cluster_provider.py +3 -9
- parsl/providers/condor/condor.py +7 -17
- parsl/providers/errors.py +2 -2
- parsl/providers/googlecloud/googlecloud.py +2 -1
- parsl/providers/grid_engine/grid_engine.py +5 -14
- parsl/providers/kubernetes/kube.py +80 -40
- parsl/providers/local/local.py +13 -26
- parsl/providers/lsf/lsf.py +5 -23
- parsl/providers/pbspro/pbspro.py +5 -17
- parsl/providers/slurm/slurm.py +81 -39
- parsl/providers/torque/torque.py +3 -14
- parsl/serialize/__init__.py +8 -3
- parsl/serialize/base.py +1 -2
- parsl/serialize/concretes.py +5 -4
- parsl/serialize/facade.py +3 -3
- parsl/serialize/proxystore.py +3 -2
- parsl/tests/__init__.py +1 -1
- parsl/tests/configs/azure_single_node.py +4 -5
- parsl/tests/configs/bridges.py +3 -2
- parsl/tests/configs/cc_in2p3.py +1 -3
- parsl/tests/configs/comet.py +2 -1
- parsl/tests/configs/ec2_single_node.py +1 -2
- parsl/tests/configs/ec2_spot.py +1 -2
- parsl/tests/configs/flux_local.py +11 -0
- parsl/tests/configs/frontera.py +2 -3
- parsl/tests/configs/htex_local.py +3 -5
- parsl/tests/configs/htex_local_alternate.py +11 -15
- parsl/tests/configs/htex_local_intask_staging.py +5 -9
- parsl/tests/configs/htex_local_rsync_staging.py +4 -8
- parsl/tests/configs/local_radical.py +1 -3
- parsl/tests/configs/local_radical_mpi.py +2 -2
- parsl/tests/configs/local_threads_checkpoint_periodic.py +8 -10
- parsl/tests/configs/local_threads_monitoring.py +0 -1
- parsl/tests/configs/midway.py +2 -2
- parsl/tests/configs/nscc_singapore.py +3 -3
- parsl/tests/configs/osg_htex.py +1 -1
- parsl/tests/configs/petrelkube.py +3 -2
- parsl/tests/configs/slurm_local.py +24 -0
- parsl/tests/configs/summit.py +1 -0
- parsl/tests/configs/taskvine_ex.py +4 -7
- parsl/tests/configs/user_opts.py +0 -7
- parsl/tests/configs/workqueue_ex.py +4 -6
- parsl/tests/conftest.py +27 -13
- parsl/tests/integration/test_stress/test_python_simple.py +3 -4
- parsl/tests/integration/test_stress/test_python_threads.py +3 -5
- parsl/tests/manual_tests/htex_local.py +4 -6
- parsl/tests/manual_tests/test_basic.py +1 -0
- parsl/tests/manual_tests/test_log_filter.py +3 -1
- parsl/tests/manual_tests/test_memory_limits.py +6 -8
- parsl/tests/manual_tests/test_regression_220.py +2 -1
- parsl/tests/manual_tests/test_udp_simple.py +4 -4
- parsl/tests/manual_tests/test_worker_count.py +3 -2
- parsl/tests/scaling_tests/htex_local.py +2 -4
- parsl/tests/scaling_tests/test_scale.py +0 -9
- parsl/tests/scaling_tests/vineex_condor.py +1 -2
- parsl/tests/scaling_tests/vineex_local.py +1 -2
- parsl/tests/site_tests/site_config_selector.py +1 -6
- parsl/tests/site_tests/test_provider.py +4 -2
- parsl/tests/site_tests/test_site.py +2 -0
- parsl/tests/sites/test_affinity.py +7 -7
- parsl/tests/sites/test_dynamic_executor.py +3 -4
- parsl/tests/sites/test_ec2.py +3 -2
- parsl/tests/sites/test_worker_info.py +4 -5
- parsl/tests/test_aalst_patterns.py +0 -1
- parsl/tests/test_bash_apps/test_apptimeout.py +2 -2
- parsl/tests/test_bash_apps/test_basic.py +10 -4
- parsl/tests/test_bash_apps/test_error_codes.py +5 -7
- parsl/tests/test_bash_apps/test_inputs_default.py +25 -0
- parsl/tests/test_bash_apps/test_kwarg_storage.py +1 -1
- parsl/tests/test_bash_apps/test_memoize.py +2 -8
- parsl/tests/test_bash_apps/test_memoize_ignore_args.py +9 -14
- parsl/tests/test_bash_apps/test_memoize_ignore_args_regr.py +9 -14
- parsl/tests/test_bash_apps/test_multiline.py +1 -1
- parsl/tests/test_bash_apps/test_pipeline.py +1 -1
- parsl/tests/test_bash_apps/test_std_uri.py +123 -0
- parsl/tests/test_bash_apps/test_stdout.py +33 -8
- parsl/tests/test_callables.py +2 -2
- parsl/tests/test_checkpointing/test_periodic.py +21 -39
- parsl/tests/test_checkpointing/test_python_checkpoint_1.py +1 -0
- parsl/tests/test_checkpointing/test_python_checkpoint_2.py +2 -2
- parsl/tests/test_checkpointing/test_python_checkpoint_3.py +0 -1
- parsl/tests/test_checkpointing/test_regression_239.py +1 -1
- parsl/tests/test_checkpointing/test_task_exit.py +2 -3
- parsl/tests/test_docs/test_from_slides.py +5 -2
- parsl/tests/test_docs/test_kwargs.py +4 -1
- parsl/tests/test_docs/test_tutorial_1.py +1 -2
- parsl/tests/test_docs/test_workflow1.py +2 -2
- parsl/tests/test_docs/test_workflow2.py +0 -1
- parsl/tests/test_error_handling/test_rand_fail.py +2 -2
- parsl/tests/test_error_handling/test_resource_spec.py +10 -12
- parsl/tests/test_error_handling/test_retries.py +6 -16
- parsl/tests/test_error_handling/test_retry_handler.py +1 -0
- parsl/tests/test_error_handling/test_retry_handler_failure.py +2 -1
- parsl/tests/test_error_handling/test_serialization_fail.py +1 -1
- parsl/tests/test_error_handling/test_wrap_with_logs.py +1 -0
- parsl/tests/test_execute_task.py +29 -0
- parsl/tests/test_flux.py +1 -1
- parsl/tests/test_htex/test_basic.py +2 -3
- parsl/tests/test_htex/test_block_manager_selector_unit.py +20 -0
- parsl/tests/test_htex/test_command_client_timeout.py +66 -0
- parsl/tests/test_htex/test_connected_blocks.py +3 -2
- parsl/tests/test_htex/test_cpu_affinity_explicit.py +6 -10
- parsl/tests/test_htex/test_disconnected_blocks.py +6 -5
- parsl/tests/test_htex/test_disconnected_blocks_failing_provider.py +71 -0
- parsl/tests/test_htex/test_drain.py +11 -10
- parsl/tests/test_htex/test_htex.py +51 -25
- parsl/tests/test_htex/test_manager_failure.py +0 -1
- parsl/tests/test_htex/test_manager_selector_by_block.py +51 -0
- parsl/tests/test_htex/test_managers_command.py +36 -0
- parsl/tests/test_htex/test_missing_worker.py +2 -12
- parsl/tests/test_htex/test_multiple_disconnected_blocks.py +9 -9
- parsl/tests/test_htex/test_resource_spec_validation.py +45 -0
- parsl/tests/test_htex/test_zmq_binding.py +29 -8
- parsl/tests/test_monitoring/test_app_names.py +5 -5
- parsl/tests/test_monitoring/test_basic.py +73 -25
- parsl/tests/test_monitoring/test_db_locks.py +6 -4
- parsl/tests/test_monitoring/test_fuzz_zmq.py +19 -8
- parsl/tests/test_monitoring/test_htex_init_blocks_vs_monitoring.py +80 -0
- parsl/tests/test_monitoring/test_incomplete_futures.py +5 -4
- parsl/tests/test_monitoring/test_memoization_representation.py +4 -2
- parsl/tests/test_monitoring/test_stdouterr.py +134 -0
- parsl/tests/test_monitoring/test_viz_colouring.py +1 -0
- parsl/tests/test_mpi_apps/test_bad_mpi_config.py +33 -26
- parsl/tests/test_mpi_apps/test_mpi_mode_enabled.py +28 -11
- parsl/tests/test_mpi_apps/test_mpi_prefix.py +4 -4
- parsl/tests/test_mpi_apps/test_mpi_scheduler.py +7 -2
- parsl/tests/test_mpi_apps/test_mpiex.py +64 -0
- parsl/tests/test_mpi_apps/test_resource_spec.py +42 -49
- parsl/tests/test_providers/test_kubernetes_provider.py +102 -0
- parsl/tests/test_providers/test_local_provider.py +3 -132
- parsl/tests/test_providers/test_pbspro_template.py +2 -3
- parsl/tests/test_providers/test_slurm_template.py +2 -3
- parsl/tests/test_providers/test_submiterror_deprecation.py +2 -1
- parsl/tests/test_python_apps/test_context_manager.py +128 -0
- parsl/tests/test_python_apps/test_dep_standard_futures.py +2 -1
- parsl/tests/test_python_apps/test_dependencies_deep.py +59 -0
- parsl/tests/test_python_apps/test_fail.py +0 -25
- parsl/tests/test_python_apps/test_futures.py +2 -1
- parsl/tests/test_python_apps/test_inputs_default.py +22 -0
- parsl/tests/test_python_apps/test_join.py +0 -1
- parsl/tests/test_python_apps/test_lifted.py +11 -7
- parsl/tests/test_python_apps/test_memoize_bad_id_for_memo.py +1 -0
- parsl/tests/test_python_apps/test_outputs.py +1 -1
- parsl/tests/test_python_apps/test_pluggable_future_resolution.py +161 -0
- parsl/tests/test_radical/test_mpi_funcs.py +1 -2
- parsl/tests/test_regression/test_1480.py +2 -1
- parsl/tests/test_regression/test_1653.py +2 -1
- parsl/tests/test_regression/test_226.py +1 -0
- parsl/tests/test_regression/test_2652.py +1 -0
- parsl/tests/test_regression/test_69a.py +0 -1
- parsl/tests/test_regression/test_854.py +4 -2
- parsl/tests/test_regression/test_97_parallelism_0.py +1 -2
- parsl/tests/test_regression/test_98.py +0 -1
- parsl/tests/test_scaling/test_block_error_handler.py +9 -4
- parsl/tests/test_scaling/test_regression_1621.py +11 -15
- parsl/tests/test_scaling/test_regression_3568_scaledown_vs_MISSING.py +84 -0
- parsl/tests/test_scaling/test_regression_3696_oscillation.py +103 -0
- parsl/tests/test_scaling/test_scale_down.py +2 -5
- parsl/tests/test_scaling/test_scale_down_htex_auto_scale.py +5 -8
- parsl/tests/test_scaling/test_scale_down_htex_unregistered.py +71 -0
- parsl/tests/test_scaling/test_shutdown_scalein.py +73 -0
- parsl/tests/test_scaling/test_worker_interchange_bad_messages_3262.py +90 -0
- parsl/tests/test_serialization/test_2555_caching_deserializer.py +1 -1
- parsl/tests/test_serialization/test_3495_deserialize_managerlost.py +47 -0
- parsl/tests/test_serialization/test_basic.py +2 -1
- parsl/tests/test_serialization/test_htex_code_cache.py +3 -4
- parsl/tests/test_serialization/test_pack_resource_spec.py +2 -1
- parsl/tests/test_serialization/test_proxystore_configured.py +10 -6
- parsl/tests/test_serialization/test_proxystore_impl.py +5 -3
- parsl/tests/test_shutdown/test_kill_monitoring.py +64 -0
- parsl/tests/test_staging/staging_provider.py +2 -2
- parsl/tests/test_staging/test_1316.py +3 -4
- parsl/tests/test_staging/test_docs_1.py +2 -1
- parsl/tests/test_staging/test_docs_2.py +2 -1
- parsl/tests/test_staging/test_elaborate_noop_file.py +2 -3
- parsl/tests/{test_data → test_staging}/test_file.py +6 -6
- parsl/tests/{test_data → test_staging}/test_output_chain_filenames.py +3 -0
- parsl/tests/test_staging/test_staging_ftp.py +1 -0
- parsl/tests/test_staging/test_staging_https.py +5 -2
- parsl/tests/test_staging/test_staging_stdout.py +64 -0
- parsl/tests/test_staging/test_zip_in.py +39 -0
- parsl/tests/test_staging/test_zip_out.py +110 -0
- parsl/tests/test_staging/test_zip_to_zip.py +41 -0
- parsl/tests/test_summary.py +2 -2
- parsl/tests/test_thread_parallelism.py +0 -1
- parsl/tests/test_threads/test_configs.py +1 -2
- parsl/tests/test_threads/test_lazy_errors.py +2 -2
- parsl/tests/test_utils/test_execute_wait.py +35 -0
- parsl/tests/test_utils/test_sanitize_dns.py +76 -0
- parsl/tests/unit/test_address.py +20 -0
- parsl/tests/unit/test_file.py +99 -0
- parsl/tests/unit/test_usage_tracking.py +66 -0
- parsl/usage_tracking/api.py +65 -0
- parsl/usage_tracking/levels.py +6 -0
- parsl/usage_tracking/usage.py +104 -62
- parsl/utils.py +137 -4
- parsl/version.py +1 -1
- {parsl-2024.3.18.data → parsl-2025.1.13.data}/scripts/exec_parsl_function.py +6 -5
- parsl-2025.1.13.data/scripts/interchange.py +649 -0
- {parsl-2024.3.18.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +77 -75
- parsl-2025.1.13.dist-info/METADATA +96 -0
- parsl-2025.1.13.dist-info/RECORD +462 -0
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/WHEEL +1 -1
- parsl/channels/__init__.py +0 -7
- parsl/channels/base.py +0 -141
- parsl/channels/errors.py +0 -113
- parsl/channels/local/local.py +0 -164
- parsl/channels/oauth_ssh/oauth_ssh.py +0 -110
- parsl/channels/ssh/ssh.py +0 -276
- parsl/channels/ssh_il/__init__.py +0 -0
- parsl/channels/ssh_il/ssh_il.py +0 -74
- parsl/configs/ad_hoc.py +0 -35
- parsl/executors/radical/rpex_master.py +0 -42
- parsl/monitoring/radios.py +0 -175
- parsl/providers/ad_hoc/__init__.py +0 -0
- parsl/providers/ad_hoc/ad_hoc.py +0 -248
- parsl/providers/cobalt/__init__.py +0 -0
- parsl/providers/cobalt/cobalt.py +0 -236
- parsl/providers/cobalt/template.py +0 -17
- parsl/tests/configs/ad_hoc_cluster_htex.py +0 -35
- parsl/tests/configs/cooley_htex.py +0 -37
- parsl/tests/configs/htex_ad_hoc_cluster.py +0 -28
- parsl/tests/configs/local_adhoc.py +0 -18
- parsl/tests/configs/swan_htex.py +0 -43
- parsl/tests/configs/theta.py +0 -37
- parsl/tests/integration/test_channels/__init__.py +0 -0
- parsl/tests/integration/test_channels/test_channels.py +0 -17
- parsl/tests/integration/test_channels/test_local_channel.py +0 -42
- parsl/tests/integration/test_channels/test_scp_1.py +0 -45
- parsl/tests/integration/test_channels/test_ssh_1.py +0 -40
- parsl/tests/integration/test_channels/test_ssh_errors.py +0 -46
- parsl/tests/integration/test_channels/test_ssh_file_transport.py +0 -41
- parsl/tests/integration/test_channels/test_ssh_interactive.py +0 -24
- parsl/tests/manual_tests/test_ad_hoc_htex.py +0 -48
- parsl/tests/manual_tests/test_fan_in_out_htex_remote.py +0 -88
- parsl/tests/manual_tests/test_oauth_ssh.py +0 -13
- parsl/tests/sites/test_local_adhoc.py +0 -61
- parsl/tests/test_channels/__init__.py +0 -0
- parsl/tests/test_channels/test_large_output.py +0 -22
- parsl/tests/test_data/__init__.py +0 -0
- parsl/tests/test_mpi_apps/test_mpi_mode_disabled.py +0 -51
- parsl/tests/test_providers/test_cobalt_deprecation_warning.py +0 -16
- parsl-2024.3.18.dist-info/METADATA +0 -98
- parsl-2024.3.18.dist-info/RECORD +0 -449
- parsl/{channels/local → monitoring/radios}/__init__.py +0 -0
- parsl/{channels/oauth_ssh → tests/test_shutdown}/__init__.py +0 -0
- parsl/tests/{test_data → test_staging}/test_file_apps.py +0 -0
- parsl/tests/{test_data → test_staging}/test_file_staging.py +0 -0
- parsl/{channels/ssh → tests/unit}/__init__.py +0 -0
- {parsl-2024.3.18.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
- {parsl-2024.3.18.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
@@ -1,41 +1,36 @@
|
|
1
1
|
import contextlib
|
2
2
|
import logging
|
3
3
|
import os
|
4
|
+
import queue
|
4
5
|
import typing
|
5
|
-
|
6
|
+
import unittest
|
7
|
+
from typing import Dict
|
8
|
+
from unittest import mock
|
6
9
|
|
7
10
|
import pytest
|
8
|
-
import unittest
|
9
11
|
|
10
|
-
import parsl
|
11
12
|
from parsl.app.app import python_app
|
12
|
-
from parsl.
|
13
|
-
from
|
13
|
+
from parsl.executors.errors import InvalidResourceSpecification
|
14
|
+
from parsl.executors.high_throughput.executor import HighThroughputExecutor
|
15
|
+
from parsl.executors.high_throughput.mpi_executor import MPIExecutor
|
14
16
|
from parsl.executors.high_throughput.mpi_resource_management import (
|
17
|
+
get_nodes_in_batchjob,
|
15
18
|
get_pbs_hosts_list,
|
16
19
|
get_slurm_hosts_list,
|
17
|
-
get_nodes_in_batchjob,
|
18
20
|
identify_scheduler,
|
19
21
|
)
|
20
|
-
from parsl.
|
21
|
-
|
22
|
-
|
23
|
-
)
|
22
|
+
from parsl.launchers import SimpleLauncher
|
23
|
+
from parsl.providers import LocalProvider
|
24
|
+
from parsl.tests.configs.htex_local import fresh_config
|
24
25
|
|
25
26
|
EXECUTOR_LABEL = "MPI_TEST"
|
26
27
|
|
27
28
|
|
28
|
-
def
|
29
|
+
def local_config():
|
29
30
|
config = fresh_config()
|
30
31
|
config.executors[0].label = EXECUTOR_LABEL
|
31
32
|
config.executors[0].max_workers_per_node = 1
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def local_teardown():
|
36
|
-
logging.warning("Exiting")
|
37
|
-
parsl.dfk().cleanup()
|
38
|
-
parsl.clear()
|
33
|
+
return config
|
39
34
|
|
40
35
|
|
41
36
|
@python_app
|
@@ -54,23 +49,6 @@ def get_env_vars(parsl_resource_specification: Dict = {}) -> Dict:
|
|
54
49
|
return parsl_vars
|
55
50
|
|
56
51
|
|
57
|
-
@pytest.mark.local
|
58
|
-
def test_resource_spec_env_vars():
|
59
|
-
resource_spec = {
|
60
|
-
"num_nodes": 4,
|
61
|
-
"ranks_per_node": 2,
|
62
|
-
}
|
63
|
-
|
64
|
-
assert double(5).result() == 10
|
65
|
-
|
66
|
-
future = get_env_vars(parsl_resource_specification=resource_spec)
|
67
|
-
|
68
|
-
result = future.result()
|
69
|
-
assert isinstance(result, Dict)
|
70
|
-
assert result["PARSL_NUM_NODES"] == str(resource_spec["num_nodes"])
|
71
|
-
assert result["PARSL_RANKS_PER_NODE"] == str(resource_spec["ranks_per_node"])
|
72
|
-
|
73
|
-
|
74
52
|
@pytest.mark.local
|
75
53
|
@unittest.mock.patch("subprocess.check_output", return_value=b"c203-031\nc203-032\n")
|
76
54
|
def test_slurm_mocked_mpi_fetch(subprocess_check):
|
@@ -89,16 +67,6 @@ def add_to_path(path: os.PathLike) -> typing.Generator[None, None, None]:
|
|
89
67
|
os.environ["PATH"] = old_path
|
90
68
|
|
91
69
|
|
92
|
-
@pytest.mark.local
|
93
|
-
@pytest.mark.skip
|
94
|
-
def test_slurm_mpi_fetch():
|
95
|
-
logging.warning(f"Current pwd : {os.path.dirname(__file__)}")
|
96
|
-
with add_to_path(os.path.dirname(__file__)):
|
97
|
-
logging.warning(f"PATH: {os.environ['PATH']}")
|
98
|
-
nodeinfo = get_slurm_hosts_list()
|
99
|
-
logging.warning(f"Got : {nodeinfo}")
|
100
|
-
|
101
|
-
|
102
70
|
@contextlib.contextmanager
|
103
71
|
def mock_pbs_nodefile(nodefile: str = "pbs_nodefile") -> typing.Generator[None, None, None]:
|
104
72
|
cwd = os.path.abspath(os.path.dirname(__file__))
|
@@ -130,16 +98,41 @@ def test_top_level():
|
|
130
98
|
@pytest.mark.parametrize(
|
131
99
|
"resource_spec, exception",
|
132
100
|
(
|
101
|
+
|
133
102
|
({"num_nodes": 2, "ranks_per_node": 1}, None),
|
134
103
|
({"launcher_options": "--debug_foo"}, None),
|
135
104
|
({"num_nodes": 2, "BAD_OPT": 1}, InvalidResourceSpecification),
|
136
|
-
({},
|
105
|
+
({}, InvalidResourceSpecification),
|
137
106
|
)
|
138
107
|
)
|
139
|
-
def
|
108
|
+
def test_mpi_resource_spec(resource_spec: Dict, exception):
|
109
|
+
"""Test validation of resource_specification in MPIExecutor"""
|
110
|
+
|
111
|
+
mpi_ex = MPIExecutor(provider=LocalProvider(launcher=SimpleLauncher()))
|
112
|
+
mpi_ex.outgoing_q = mock.Mock(spec=queue.Queue)
|
113
|
+
|
140
114
|
if exception:
|
141
115
|
with pytest.raises(exception):
|
142
|
-
validate_resource_spec(resource_spec)
|
116
|
+
mpi_ex.validate_resource_spec(resource_spec)
|
143
117
|
else:
|
144
|
-
result = validate_resource_spec(resource_spec)
|
118
|
+
result = mpi_ex.validate_resource_spec(resource_spec)
|
145
119
|
assert result is None
|
120
|
+
|
121
|
+
|
122
|
+
@pytest.mark.local
|
123
|
+
@pytest.mark.parametrize(
|
124
|
+
"resource_spec",
|
125
|
+
(
|
126
|
+
{"num_nodes": 2, "ranks_per_node": 1},
|
127
|
+
{"launcher_options": "--debug_foo"},
|
128
|
+
{"BAD_OPT": 1},
|
129
|
+
)
|
130
|
+
)
|
131
|
+
def test_mpi_resource_spec_passed_to_htex(resource_spec: dict):
|
132
|
+
"""HTEX should reject every resource_spec"""
|
133
|
+
|
134
|
+
htex = HighThroughputExecutor()
|
135
|
+
htex.outgoing_q = mock.Mock(spec=queue.Queue)
|
136
|
+
|
137
|
+
with pytest.raises(InvalidResourceSpecification):
|
138
|
+
htex.validate_resource_spec(resource_spec)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import re
|
2
|
+
from unittest import mock
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
from parsl.providers.kubernetes.kube import KubernetesProvider
|
7
|
+
from parsl.tests.test_utils.test_sanitize_dns import DNS_SUBDOMAIN_REGEX
|
8
|
+
|
9
|
+
_MOCK_BASE = "parsl.providers.kubernetes.kube"
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.fixture(autouse=True)
|
13
|
+
def mock_kube_config():
|
14
|
+
with mock.patch(f"{_MOCK_BASE}.config") as mock_config:
|
15
|
+
mock_config.load_kube_config.return_value = None
|
16
|
+
yield mock_config
|
17
|
+
|
18
|
+
|
19
|
+
@pytest.fixture
|
20
|
+
def mock_kube_client():
|
21
|
+
mock_client = mock.MagicMock()
|
22
|
+
with mock.patch(f"{_MOCK_BASE}.client.CoreV1Api") as mock_api:
|
23
|
+
mock_api.return_value = mock_client
|
24
|
+
yield mock_client
|
25
|
+
|
26
|
+
|
27
|
+
@pytest.mark.local
|
28
|
+
def test_submit_happy_path(mock_kube_client: mock.MagicMock):
|
29
|
+
image = "test-image"
|
30
|
+
namespace = "test-namespace"
|
31
|
+
cmd_string = "test-command"
|
32
|
+
volumes = [("test-volume", "test-mount-path")]
|
33
|
+
service_account_name = "test-service-account"
|
34
|
+
annotations = {"test-annotation": "test-value"}
|
35
|
+
max_cpu = 2
|
36
|
+
max_mem = "2Gi"
|
37
|
+
init_cpu = 1
|
38
|
+
init_mem = "1Gi"
|
39
|
+
provider = KubernetesProvider(
|
40
|
+
image=image,
|
41
|
+
persistent_volumes=volumes,
|
42
|
+
namespace=namespace,
|
43
|
+
service_account_name=service_account_name,
|
44
|
+
annotations=annotations,
|
45
|
+
max_cpu=max_cpu,
|
46
|
+
max_mem=max_mem,
|
47
|
+
init_cpu=init_cpu,
|
48
|
+
init_mem=init_mem,
|
49
|
+
)
|
50
|
+
|
51
|
+
job_name = "test.job.name"
|
52
|
+
job_id = provider.submit(cmd_string=cmd_string, tasks_per_node=1, job_name=job_name)
|
53
|
+
|
54
|
+
assert job_id in provider.resources
|
55
|
+
assert mock_kube_client.create_namespaced_pod.call_count == 1
|
56
|
+
|
57
|
+
call_args = mock_kube_client.create_namespaced_pod.call_args[1]
|
58
|
+
pod = call_args["body"]
|
59
|
+
container = pod.spec.containers[0]
|
60
|
+
volume = container.volume_mounts[0]
|
61
|
+
|
62
|
+
assert image == container.image
|
63
|
+
assert namespace == call_args["namespace"]
|
64
|
+
assert any(cmd_string in arg for arg in container.args)
|
65
|
+
assert volumes[0] == (volume.name, volume.mount_path)
|
66
|
+
assert service_account_name == pod.spec.service_account_name
|
67
|
+
assert annotations == pod.metadata.annotations
|
68
|
+
assert str(max_cpu) == container.resources.limits["cpu"]
|
69
|
+
assert max_mem == container.resources.limits["memory"]
|
70
|
+
assert str(init_cpu) == container.resources.requests["cpu"]
|
71
|
+
assert init_mem == container.resources.requests["memory"]
|
72
|
+
assert job_id == pod.metadata.labels["parsl-job-id"]
|
73
|
+
assert job_id == container.name
|
74
|
+
assert f"{job_name}.{job_id}" == pod.metadata.name
|
75
|
+
|
76
|
+
|
77
|
+
@pytest.mark.local
|
78
|
+
@mock.patch(f"{_MOCK_BASE}.KubernetesProvider._create_pod")
|
79
|
+
@pytest.mark.parametrize("char", (".", "-"))
|
80
|
+
def test_submit_pod_name_includes_job_id(mock_create_pod: mock.MagicMock, char: str):
|
81
|
+
provider = KubernetesProvider(image="test-image")
|
82
|
+
|
83
|
+
job_name = "a." * 121 + f"a{char}" + "a" * 9
|
84
|
+
assert len(job_name) == 253 # Max length for pod name
|
85
|
+
job_id = provider.submit(cmd_string="test-command", tasks_per_node=1, job_name=job_name)
|
86
|
+
|
87
|
+
expected_pod_name = job_name[:253 - len(job_id) - 2] + f".{job_id}"
|
88
|
+
actual_pod_name = mock_create_pod.call_args[1]["pod_name"]
|
89
|
+
assert re.match(DNS_SUBDOMAIN_REGEX, actual_pod_name)
|
90
|
+
assert expected_pod_name == actual_pod_name
|
91
|
+
|
92
|
+
|
93
|
+
@pytest.mark.local
|
94
|
+
@mock.patch(f"{_MOCK_BASE}.KubernetesProvider._create_pod")
|
95
|
+
@mock.patch(f"{_MOCK_BASE}.logger")
|
96
|
+
@pytest.mark.parametrize("job_name", ("", ".", "-", "a.-.a", "$$$"))
|
97
|
+
def test_submit_invalid_job_name(mock_logger: mock.MagicMock, mock_create_pod: mock.MagicMock, job_name: str):
|
98
|
+
provider = KubernetesProvider(image="test-image")
|
99
|
+
job_id = provider.submit(cmd_string="test-command", tasks_per_node=1, job_name=job_name)
|
100
|
+
assert mock_logger.warning.call_count == 1
|
101
|
+
assert f"Invalid pod name '{job_name}' for job '{job_id}'" in mock_logger.warning.call_args[0][0]
|
102
|
+
assert f"parsl.kube.{job_id}" == mock_create_pod.call_args[1]["pod_name"]
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
import pathlib
|
4
|
-
import pytest
|
5
4
|
import random
|
6
5
|
import shutil
|
7
6
|
import socket
|
@@ -10,7 +9,8 @@ import tempfile
|
|
10
9
|
import threading
|
11
10
|
import time
|
12
11
|
|
13
|
-
|
12
|
+
import pytest
|
13
|
+
|
14
14
|
from parsl.jobs.states import JobState
|
15
15
|
from parsl.launchers import SingleNodeLauncher
|
16
16
|
from parsl.providers import LocalProvider
|
@@ -62,140 +62,11 @@ def _run_tests(p: LocalProvider):
|
|
62
62
|
def test_local_channel():
|
63
63
|
with tempfile.TemporaryDirectory() as script_dir:
|
64
64
|
script_dir = tempfile.mkdtemp()
|
65
|
-
p = LocalProvider(
|
65
|
+
p = LocalProvider(launcher=SingleNodeLauncher(debug=False))
|
66
66
|
p.script_dir = script_dir
|
67
67
|
_run_tests(p)
|
68
68
|
|
69
69
|
|
70
|
-
SSHD_CONFIG = """
|
71
|
-
Port {port}
|
72
|
-
ListenAddress 127.0.0.1
|
73
|
-
HostKey {hostkey}
|
74
|
-
AuthorizedKeysFile {connpubkey}
|
75
|
-
AuthenticationMethods publickey
|
76
|
-
StrictModes no
|
77
|
-
Subsystem sftp {sftp_path}
|
78
|
-
"""
|
79
|
-
|
80
|
-
|
81
|
-
# It would probably be better, when more formalized site testing comes into existence, to
|
82
|
-
# use a site-testing provided server/configuration instead of the current scheme
|
83
|
-
@pytest.mark.local
|
84
|
-
@pytest.mark.sshd_required
|
85
|
-
def test_ssh_channel():
|
86
|
-
with tempfile.TemporaryDirectory() as config_dir:
|
87
|
-
sshd_thread, priv_key, server_port = _start_sshd(config_dir)
|
88
|
-
try:
|
89
|
-
with tempfile.TemporaryDirectory() as remote_script_dir:
|
90
|
-
# The SSH library fails to add the new host key to the file if the file does not
|
91
|
-
# already exist, so create it here.
|
92
|
-
pathlib.Path('{}/known.hosts'.format(config_dir)).touch(mode=0o600)
|
93
|
-
script_dir = tempfile.mkdtemp()
|
94
|
-
p = LocalProvider(channel=SSHChannel('127.0.0.1', port=server_port,
|
95
|
-
script_dir=remote_script_dir,
|
96
|
-
host_keys_filename='{}/known.hosts'.format(config_dir),
|
97
|
-
key_filename=priv_key),
|
98
|
-
launcher=SingleNodeLauncher(debug=False))
|
99
|
-
p.script_dir = script_dir
|
100
|
-
_run_tests(p)
|
101
|
-
finally:
|
102
|
-
_stop_sshd(sshd_thread)
|
103
|
-
|
104
|
-
|
105
|
-
def _stop_sshd(sshd_thread):
|
106
|
-
sshd_thread.stop()
|
107
|
-
|
108
|
-
|
109
|
-
class SSHDThread(threading.Thread):
|
110
|
-
def __init__(self, config_file):
|
111
|
-
threading.Thread.__init__(self, daemon=True)
|
112
|
-
self.config_file = config_file
|
113
|
-
self.stop_flag = False
|
114
|
-
self.error = None
|
115
|
-
|
116
|
-
def run(self):
|
117
|
-
try:
|
118
|
-
# sshd needs to be run with an absolute path, hence the call to which()
|
119
|
-
sshpath = shutil.which('sshd')
|
120
|
-
assert sshpath is not None, "can find sshd executable"
|
121
|
-
p = subprocess.Popen([sshpath, '-D', '-f', self.config_file],
|
122
|
-
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
123
|
-
while True:
|
124
|
-
ec = p.poll()
|
125
|
-
if self.stop_flag:
|
126
|
-
p.terminate()
|
127
|
-
break
|
128
|
-
elif ec is None:
|
129
|
-
time.sleep(0.1)
|
130
|
-
elif ec == 0:
|
131
|
-
self.error = Exception('sshd exited prematurely: {}{}'.format(p.stdout.read(),
|
132
|
-
p.stderr.read()))
|
133
|
-
break
|
134
|
-
else:
|
135
|
-
self.error = Exception('sshd failed: {}{}'.format(p.stdout.read(),
|
136
|
-
p.stderr.read()))
|
137
|
-
break
|
138
|
-
except Exception as ex:
|
139
|
-
logger.exception("SSHDThread exception from run loop")
|
140
|
-
self.error = ex
|
141
|
-
|
142
|
-
def stop(self):
|
143
|
-
self.stop_flag = True
|
144
|
-
|
145
|
-
|
146
|
-
def _start_sshd(config_dir: str):
|
147
|
-
server_config, priv_key, port = _init_sshd(config_dir)
|
148
|
-
sshd_thread = SSHDThread(server_config)
|
149
|
-
sshd_thread.start()
|
150
|
-
time.sleep(1.0)
|
151
|
-
if not sshd_thread.is_alive():
|
152
|
-
raise Exception('Failed to start sshd: {}'.format(sshd_thread.error))
|
153
|
-
return sshd_thread, priv_key, port
|
154
|
-
|
155
|
-
|
156
|
-
def _init_sshd(config_dir):
|
157
|
-
hostkey = '{}/hostkey'.format(config_dir)
|
158
|
-
connkey = '{}/connkey'.format(config_dir)
|
159
|
-
os.system('ssh-keygen -b 2048 -t rsa -q -N "" -f {}'.format(hostkey))
|
160
|
-
os.system('ssh-keygen -b 2048 -t rsa -q -N "" -f {}'.format(connkey))
|
161
|
-
port = _find_free_port(22222)
|
162
|
-
server_config_str = SSHD_CONFIG.format(port=port, hostkey=hostkey,
|
163
|
-
connpubkey='{}.pub'.format(connkey),
|
164
|
-
sftp_path=_get_system_sftp_path())
|
165
|
-
server_config = '{}/sshd_config'.format(config_dir)
|
166
|
-
with open(server_config, 'w') as f:
|
167
|
-
f.write(server_config_str)
|
168
|
-
return server_config, connkey, port
|
169
|
-
|
170
|
-
|
171
|
-
def _get_system_sftp_path():
|
172
|
-
try:
|
173
|
-
with open('/etc/ssh/sshd_config') as f:
|
174
|
-
line = f.readline()
|
175
|
-
while line:
|
176
|
-
tokens = line.split()
|
177
|
-
if tokens[0] == 'Subsystem' and tokens[1] == 'sftp':
|
178
|
-
return tokens[2]
|
179
|
-
line = f.readline()
|
180
|
-
except Exception:
|
181
|
-
pass
|
182
|
-
return '/usr/lib/openssh/sftp-server'
|
183
|
-
|
184
|
-
|
185
|
-
def _find_free_port(start: int):
|
186
|
-
port = start
|
187
|
-
while port < 65535:
|
188
|
-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
189
|
-
try:
|
190
|
-
s.bind(('127.0.0.1', port))
|
191
|
-
s.close()
|
192
|
-
return port
|
193
|
-
except Exception:
|
194
|
-
pass
|
195
|
-
port += random.randint(1, 20)
|
196
|
-
raise Exception('Could not find free port')
|
197
|
-
|
198
|
-
|
199
70
|
def _run(p: LocalProvider, command: str, np: int = 1):
|
200
71
|
id = p.submit(command, np)
|
201
72
|
return _wait(p, id)
|
@@ -1,9 +1,8 @@
|
|
1
1
|
import random
|
2
|
-
|
3
2
|
from unittest import mock
|
3
|
+
|
4
4
|
import pytest
|
5
5
|
|
6
|
-
from parsl.channels import LocalChannel
|
7
6
|
from parsl.providers import PBSProProvider
|
8
7
|
|
9
8
|
|
@@ -12,7 +11,7 @@ def test_submit_script_basic(tmp_path):
|
|
12
11
|
"""Test slurm resources table"""
|
13
12
|
|
14
13
|
provider = PBSProProvider(
|
15
|
-
queue="debug"
|
14
|
+
queue="debug"
|
16
15
|
)
|
17
16
|
provider.script_dir = tmp_path
|
18
17
|
job_id = str(random.randint(55000, 59000))
|
@@ -1,10 +1,9 @@
|
|
1
1
|
import logging
|
2
2
|
import random
|
3
|
-
|
4
3
|
from unittest import mock
|
4
|
+
|
5
5
|
import pytest
|
6
6
|
|
7
|
-
from parsl.channels import LocalChannel
|
8
7
|
from parsl.providers import SlurmProvider
|
9
8
|
|
10
9
|
|
@@ -13,7 +12,7 @@ def test_submit_script_basic(tmp_path):
|
|
13
12
|
"""Test slurm resources table"""
|
14
13
|
|
15
14
|
provider = SlurmProvider(
|
16
|
-
partition="debug"
|
15
|
+
partition="debug"
|
17
16
|
)
|
18
17
|
provider.script_dir = tmp_path
|
19
18
|
job_id = str(random.randint(55000, 59000))
|
@@ -0,0 +1,128 @@
|
|
1
|
+
from concurrent.futures import Future
|
2
|
+
from threading import Event
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
import parsl
|
7
|
+
from parsl.config import Config
|
8
|
+
from parsl.dataflow.dflow import DataFlowKernel, DataFlowKernelLoader
|
9
|
+
from parsl.errors import NoDataFlowKernelError
|
10
|
+
from parsl.tests.configs.local_threads import fresh_config
|
11
|
+
|
12
|
+
|
13
|
+
@parsl.python_app
|
14
|
+
def square(x):
|
15
|
+
return x * x
|
16
|
+
|
17
|
+
|
18
|
+
@parsl.bash_app
|
19
|
+
def foo(x, stdout='foo.stdout'):
|
20
|
+
return f"echo {x + 1}"
|
21
|
+
|
22
|
+
|
23
|
+
@parsl.python_app
|
24
|
+
def wait_for_event(ev: Event):
|
25
|
+
ev.wait()
|
26
|
+
|
27
|
+
|
28
|
+
@parsl.python_app
|
29
|
+
def raise_app():
|
30
|
+
raise RuntimeError("raise_app deliberate failure")
|
31
|
+
|
32
|
+
|
33
|
+
@pytest.mark.local
|
34
|
+
def test_within_context_manger(tmpd_cwd):
|
35
|
+
config = fresh_config()
|
36
|
+
with parsl.load(config=config) as dfk:
|
37
|
+
assert isinstance(dfk, DataFlowKernel)
|
38
|
+
|
39
|
+
bash_future = foo(1, stdout=tmpd_cwd / 'foo.stdout')
|
40
|
+
assert bash_future.result() == 0
|
41
|
+
|
42
|
+
with open(tmpd_cwd / 'foo.stdout', 'r') as f:
|
43
|
+
assert f.read() == "2\n"
|
44
|
+
|
45
|
+
with pytest.raises(NoDataFlowKernelError) as excinfo:
|
46
|
+
square(2).result()
|
47
|
+
assert str(excinfo.value) == "Must first load config"
|
48
|
+
|
49
|
+
|
50
|
+
@pytest.mark.local
|
51
|
+
def test_exit_skip():
|
52
|
+
config = fresh_config()
|
53
|
+
config.exit_mode = "skip"
|
54
|
+
|
55
|
+
with parsl.load(config) as dfk:
|
56
|
+
ev = Event()
|
57
|
+
fut = wait_for_event(ev)
|
58
|
+
# deliberately don't wait for this to finish, so that the context
|
59
|
+
# manager can exit
|
60
|
+
|
61
|
+
assert parsl.dfk() is dfk, "global dfk should be left in place by skip mode"
|
62
|
+
|
63
|
+
assert not fut.done(), "wait_for_event should not be done yet"
|
64
|
+
ev.set()
|
65
|
+
|
66
|
+
# now we can wait for that result...
|
67
|
+
fut.result()
|
68
|
+
assert fut.done(), "wait_for_event should complete outside of context manager in 'skip' mode"
|
69
|
+
|
70
|
+
# now cleanup the DFK that the above `with` block
|
71
|
+
# deliberately avoided doing...
|
72
|
+
dfk.cleanup()
|
73
|
+
|
74
|
+
|
75
|
+
# 'wait' mode has two cases to test:
|
76
|
+
# 1. that we wait when there is no exception
|
77
|
+
# 2. that we do not wait when there is an exception
|
78
|
+
@pytest.mark.local
|
79
|
+
def test_exit_wait_no_exception():
|
80
|
+
config = fresh_config()
|
81
|
+
config.exit_mode = "wait"
|
82
|
+
|
83
|
+
with parsl.load(config) as dfk:
|
84
|
+
fut = square(1)
|
85
|
+
# deliberately don't wait for this to finish, so that the context
|
86
|
+
# manager can exit
|
87
|
+
|
88
|
+
assert fut.done(), "This future should be marked as done before the context manager exits"
|
89
|
+
|
90
|
+
assert dfk.cleanup_called, "The DFK should have been cleaned up by the context manager"
|
91
|
+
assert DataFlowKernelLoader._dfk is None, "The global DFK should have been removed"
|
92
|
+
|
93
|
+
|
94
|
+
@pytest.mark.local
|
95
|
+
def test_exit_wait_exception():
|
96
|
+
config = fresh_config()
|
97
|
+
config.exit_mode = "wait"
|
98
|
+
|
99
|
+
with pytest.raises(RuntimeError):
|
100
|
+
with parsl.load(config) as dfk:
|
101
|
+
# we'll never fire this future
|
102
|
+
fut_never = Future()
|
103
|
+
|
104
|
+
fut_raise = raise_app()
|
105
|
+
|
106
|
+
fut_depend = square(fut_never)
|
107
|
+
|
108
|
+
# this should cause an exception, which should cause the context
|
109
|
+
# manager to exit, without waiting for fut_depend to finish.
|
110
|
+
fut_raise.result()
|
111
|
+
|
112
|
+
assert dfk.cleanup_called, "The DFK should have been cleaned up by the context manager"
|
113
|
+
assert DataFlowKernelLoader._dfk is None, "The global DFK should have been removed"
|
114
|
+
assert fut_raise.exception() is not None, "fut_raise should contain an exception"
|
115
|
+
assert not fut_depend.done(), "fut_depend should have been left un-done (due to dependency failure)"
|
116
|
+
|
117
|
+
|
118
|
+
@pytest.mark.local
|
119
|
+
def test_exit_wrong_mode():
|
120
|
+
|
121
|
+
with pytest.raises(Exception) as ex:
|
122
|
+
Config(exit_mode="wrongmode")
|
123
|
+
|
124
|
+
# with typeguard 4.x this is TypeCheckError,
|
125
|
+
# with typeguard 2.x this is TypeError
|
126
|
+
# we can't instantiate TypeCheckError if we're in typeguard 2.x environment
|
127
|
+
# because it does not exist... so check name using strings.
|
128
|
+
assert ex.type.__name__ == "TypeCheckError" or ex.type.__name__ == "TypeError"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import inspect
|
2
|
+
from concurrent.futures import Future
|
3
|
+
from typing import Any, Callable, Dict
|
4
|
+
|
5
|
+
import pytest
|
6
|
+
|
7
|
+
import parsl
|
8
|
+
from parsl.executors.base import ParslExecutor
|
9
|
+
|
10
|
+
# N is the number of tasks to chain
|
11
|
+
# With mid-2024 Parsl, N>140 causes Parsl to hang
|
12
|
+
N = 100
|
13
|
+
|
14
|
+
# MAX_STACK is the maximum Python stack depth allowed for either
|
15
|
+
# task submission to an executor or execution of a task.
|
16
|
+
# With mid-2024 Parsl, 2-3 stack entries will be used per
|
17
|
+
# recursively launched parsl task. So this should be smaller than
|
18
|
+
# 2*N, but big enough to allow regular pytest+parsl stuff to
|
19
|
+
# happen.
|
20
|
+
MAX_STACK = 50
|
21
|
+
|
22
|
+
|
23
|
+
def local_config():
|
24
|
+
return parsl.Config(executors=[ImmediateExecutor()])
|
25
|
+
|
26
|
+
|
27
|
+
class ImmediateExecutor(ParslExecutor):
|
28
|
+
def start(self):
|
29
|
+
pass
|
30
|
+
|
31
|
+
def shutdown(self):
|
32
|
+
pass
|
33
|
+
|
34
|
+
def submit(self, func: Callable, resource_specification: Dict[str, Any], *args: Any, **kwargs: Any) -> Future:
|
35
|
+
stack_depth = len(inspect.stack())
|
36
|
+
assert stack_depth < MAX_STACK, "tasks should not be launched deep in the Python stack"
|
37
|
+
fut: Future[None] = Future()
|
38
|
+
res = func(*args, **kwargs)
|
39
|
+
fut.set_result(res)
|
40
|
+
return fut
|
41
|
+
|
42
|
+
|
43
|
+
@parsl.python_app
|
44
|
+
def chain(upstream):
|
45
|
+
stack_depth = len(inspect.stack())
|
46
|
+
assert stack_depth < MAX_STACK, "chained dependencies should not be launched deep in the Python stack"
|
47
|
+
|
48
|
+
|
49
|
+
@pytest.mark.local
|
50
|
+
def test_deep_dependency_stack_depth():
|
51
|
+
|
52
|
+
fut = Future()
|
53
|
+
here = fut
|
54
|
+
|
55
|
+
for _ in range(N):
|
56
|
+
here = chain(here)
|
57
|
+
|
58
|
+
fut.set_result(None)
|
59
|
+
here.result()
|
@@ -41,28 +41,3 @@ def test_fail_sequence(fail_probs):
|
|
41
41
|
|
42
42
|
with pytest.raises(DependencyError):
|
43
43
|
t_final.result()
|
44
|
-
|
45
|
-
|
46
|
-
def test_deps(width=3):
|
47
|
-
"""Random failures in branches of Map -> Map -> reduce"""
|
48
|
-
# App1 App2 ... AppN
|
49
|
-
futs = [random_fail(fail_prob=0.4) for _ in range(width)]
|
50
|
-
|
51
|
-
# App1 App2 ... AppN
|
52
|
-
# | | |
|
53
|
-
# V V V
|
54
|
-
# App1 App2 ... AppN
|
55
|
-
|
56
|
-
futs = [random_fail(fail_prob=0.8, inputs=[f]) for f in futs]
|
57
|
-
|
58
|
-
# App1 App2 ... AppN
|
59
|
-
# | | |
|
60
|
-
# V V V
|
61
|
-
# App1 App2 ... AppN
|
62
|
-
# \ | /
|
63
|
-
# \ | /
|
64
|
-
# App_Final
|
65
|
-
try:
|
66
|
-
random_fail(fail_prob=0, inputs=futs).result()
|
67
|
-
except DependencyError:
|
68
|
-
pass
|
@@ -11,9 +11,10 @@ Same applies to datafutures, and we need to know the behavior wrt.
|
|
11
11
|
2. done() called on 1, vs 2
|
12
12
|
|
13
13
|
"""
|
14
|
-
import pytest
|
15
14
|
from os.path import basename
|
16
15
|
|
16
|
+
import pytest
|
17
|
+
|
17
18
|
from parsl.app.app import python_app
|
18
19
|
from parsl.data_provider.files import File
|
19
20
|
|