parsl 2024.3.11__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 +29 -7
- 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 +57 -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 +262 -224
- 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 +316 -282
- parsl/executors/high_throughput/interchange.py +158 -167
- parsl/executors/high_throughput/manager_record.py +5 -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 +115 -77
- 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 +41 -57
- parsl/executors/taskvine/factory.py +1 -1
- parsl/executors/taskvine/factory_config.py +1 -1
- parsl/executors/taskvine/manager.py +18 -13
- parsl/executors/taskvine/manager_config.py +9 -5
- 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 +30 -113
- 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 +6 -12
- parsl/log_utils.py +9 -6
- parsl/monitoring/db_manager.py +59 -95
- parsl/monitoring/errors.py +6 -0
- parsl/monitoring/monitoring.py +87 -356
- 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 +11 -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 -8
- 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 +4 -12
- 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 +2 -8
- 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 +79 -0
- 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 +86 -0
- 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 +6 -18
- 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 +139 -6
- parsl/version.py +1 -1
- {parsl-2024.3.11.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.11.data → parsl-2025.1.13.data}/scripts/process_worker_pool.py +115 -77
- parsl-2025.1.13.dist-info/METADATA +96 -0
- parsl-2025.1.13.dist-info/RECORD +462 -0
- {parsl-2024.3.11.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.11.dist-info/METADATA +0 -98
- parsl-2024.3.11.dist-info/RECORD +0 -447
- 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.11.data → parsl-2025.1.13.data}/scripts/parsl_coprocess.py +1 -1
- {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/LICENSE +0 -0
- {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/entry_points.txt +0 -0
- {parsl-2024.3.11.dist-info → parsl-2025.1.13.dist-info}/top_level.txt +0 -0
parsl/channels/errors.py
DELETED
@@ -1,113 +0,0 @@
|
|
1
|
-
''' Exceptions raise by Apps.
|
2
|
-
'''
|
3
|
-
from parsl.errors import ParslError
|
4
|
-
from typing import Optional
|
5
|
-
|
6
|
-
|
7
|
-
class ChannelError(ParslError):
|
8
|
-
""" Base class for all exceptions
|
9
|
-
|
10
|
-
Only to be invoked when only a more specific error is not available.
|
11
|
-
"""
|
12
|
-
def __init__(self, reason: str, e: Exception, hostname: str) -> None:
|
13
|
-
self.reason = reason
|
14
|
-
self.e = e
|
15
|
-
self.hostname = hostname
|
16
|
-
|
17
|
-
def __str__(self) -> str:
|
18
|
-
return "Hostname:{0}, Reason:{1}".format(self.hostname, self.reason)
|
19
|
-
|
20
|
-
|
21
|
-
class BadHostKeyException(ChannelError):
|
22
|
-
''' SSH channel could not be created since server's host keys could not
|
23
|
-
be verified
|
24
|
-
|
25
|
-
Contains:
|
26
|
-
reason(string)
|
27
|
-
e (paramiko exception object)
|
28
|
-
hostname (string)
|
29
|
-
'''
|
30
|
-
|
31
|
-
def __init__(self, e: Exception, hostname: str) -> None:
|
32
|
-
super().__init__("SSH channel could not be created since server's host keys could not be "
|
33
|
-
"verified", e, hostname)
|
34
|
-
|
35
|
-
|
36
|
-
class BadScriptPath(ChannelError):
|
37
|
-
''' An error raised during execution of an app.
|
38
|
-
What this exception contains depends entirely on context
|
39
|
-
Contains:
|
40
|
-
reason(string)
|
41
|
-
e (paramiko exception object)
|
42
|
-
hostname (string)
|
43
|
-
'''
|
44
|
-
|
45
|
-
def __init__(self, e: Exception, hostname: str) -> None:
|
46
|
-
super().__init__("Inaccessible remote script dir. Specify script_dir", e, hostname)
|
47
|
-
|
48
|
-
|
49
|
-
class BadPermsScriptPath(ChannelError):
|
50
|
-
''' User does not have permissions to access the script_dir on the remote site
|
51
|
-
|
52
|
-
Contains:
|
53
|
-
reason(string)
|
54
|
-
e (paramiko exception object)
|
55
|
-
hostname (string)
|
56
|
-
'''
|
57
|
-
|
58
|
-
def __init__(self, e: Exception, hostname: str) -> None:
|
59
|
-
super().__init__("User does not have permissions to access the script_dir", e, hostname)
|
60
|
-
|
61
|
-
|
62
|
-
class FileExists(ChannelError):
|
63
|
-
''' Push or pull of file over channel fails since a file of the name already
|
64
|
-
exists on the destination.
|
65
|
-
|
66
|
-
Contains:
|
67
|
-
reason(string)
|
68
|
-
e (paramiko exception object)
|
69
|
-
hostname (string)
|
70
|
-
'''
|
71
|
-
|
72
|
-
def __init__(self, e: Exception, hostname: str, filename: Optional[str] = None) -> None:
|
73
|
-
super().__init__("File name collision in channel transport phase: {}".format(filename),
|
74
|
-
e, hostname)
|
75
|
-
|
76
|
-
|
77
|
-
class AuthException(ChannelError):
|
78
|
-
''' An error raised during execution of an app.
|
79
|
-
What this exception contains depends entirely on context
|
80
|
-
Contains:
|
81
|
-
reason(string)
|
82
|
-
e (paramiko exception object)
|
83
|
-
hostname (string)
|
84
|
-
'''
|
85
|
-
|
86
|
-
def __init__(self, e: Exception, hostname: str) -> None:
|
87
|
-
super().__init__("Authentication to remote server failed", e, hostname)
|
88
|
-
|
89
|
-
|
90
|
-
class SSHException(ChannelError):
|
91
|
-
''' if there was any other error connecting or establishing an SSH session
|
92
|
-
|
93
|
-
Contains:
|
94
|
-
reason(string)
|
95
|
-
e (paramiko exception object)
|
96
|
-
hostname (string)
|
97
|
-
'''
|
98
|
-
|
99
|
-
def __init__(self, e: Exception, hostname: str) -> None:
|
100
|
-
super().__init__("Error connecting or establishing an SSH session", e, hostname)
|
101
|
-
|
102
|
-
|
103
|
-
class FileCopyException(ChannelError):
|
104
|
-
''' File copy operation failed
|
105
|
-
|
106
|
-
Contains:
|
107
|
-
reason(string)
|
108
|
-
e (paramiko exception object)
|
109
|
-
hostname (string)
|
110
|
-
'''
|
111
|
-
|
112
|
-
def __init__(self, e: Exception, hostname: str) -> None:
|
113
|
-
super().__init__("File copy failed due to {0}".format(e), e, hostname)
|
parsl/channels/local/local.py
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
import copy
|
2
|
-
import logging
|
3
|
-
import os
|
4
|
-
import shutil
|
5
|
-
import subprocess
|
6
|
-
|
7
|
-
from parsl.channels.base import Channel
|
8
|
-
from parsl.channels.errors import FileCopyException
|
9
|
-
from parsl.utils import RepresentationMixin
|
10
|
-
|
11
|
-
logger = logging.getLogger(__name__)
|
12
|
-
|
13
|
-
|
14
|
-
class LocalChannel(Channel, RepresentationMixin):
|
15
|
-
''' This is not even really a channel, since opening a local shell is not heavy
|
16
|
-
and done so infrequently that they do not need a persistent channel
|
17
|
-
'''
|
18
|
-
|
19
|
-
def __init__(self, userhome=".", envs={}, script_dir=None):
|
20
|
-
''' Initialize the local channel. script_dir is required by set to a default.
|
21
|
-
|
22
|
-
KwArgs:
|
23
|
-
- userhome (string): (default='.') This is provided as a way to override and set a specific userhome
|
24
|
-
- envs (dict) : A dictionary of env variables to be set when launching the shell
|
25
|
-
- script_dir (string): Directory to place scripts
|
26
|
-
'''
|
27
|
-
self.userhome = os.path.abspath(userhome)
|
28
|
-
self.hostname = "localhost"
|
29
|
-
self.envs = envs
|
30
|
-
local_env = os.environ.copy()
|
31
|
-
self._envs = copy.deepcopy(local_env)
|
32
|
-
self._envs.update(envs)
|
33
|
-
self.script_dir = script_dir
|
34
|
-
|
35
|
-
def execute_wait(self, cmd, walltime=None, envs={}):
|
36
|
-
''' Synchronously execute a commandline string on the shell.
|
37
|
-
|
38
|
-
Args:
|
39
|
-
- cmd (string) : Commandline string to execute
|
40
|
-
- walltime (int) : walltime in seconds, this is not really used now.
|
41
|
-
|
42
|
-
Kwargs:
|
43
|
-
- envs (dict) : Dictionary of env variables. This will be used
|
44
|
-
to override the envs set at channel initialization.
|
45
|
-
|
46
|
-
Returns:
|
47
|
-
- retcode : Return code from the execution, -1 on fail
|
48
|
-
- stdout : stdout string
|
49
|
-
- stderr : stderr string
|
50
|
-
|
51
|
-
Raises:
|
52
|
-
None.
|
53
|
-
'''
|
54
|
-
current_env = copy.deepcopy(self._envs)
|
55
|
-
current_env.update(envs)
|
56
|
-
|
57
|
-
try:
|
58
|
-
proc = subprocess.Popen(
|
59
|
-
cmd,
|
60
|
-
stdout=subprocess.PIPE,
|
61
|
-
stderr=subprocess.PIPE,
|
62
|
-
cwd=self.userhome,
|
63
|
-
env=current_env,
|
64
|
-
shell=True,
|
65
|
-
preexec_fn=os.setpgrp
|
66
|
-
)
|
67
|
-
(stdout, stderr) = proc.communicate(timeout=walltime)
|
68
|
-
retcode = proc.returncode
|
69
|
-
|
70
|
-
except Exception as e:
|
71
|
-
logger.warning("Execution of command '{}' failed due to \n{}".format(cmd, e))
|
72
|
-
raise
|
73
|
-
|
74
|
-
return (retcode, stdout.decode("utf-8"), stderr.decode("utf-8"))
|
75
|
-
|
76
|
-
def push_file(self, source, dest_dir):
|
77
|
-
''' If the source files dirpath is the same as dest_dir, a copy
|
78
|
-
is not necessary, and nothing is done. Else a copy is made.
|
79
|
-
|
80
|
-
Args:
|
81
|
-
- source (string) : Path to the source file
|
82
|
-
- dest_dir (string) : Path to the directory to which the files is to be copied
|
83
|
-
|
84
|
-
Returns:
|
85
|
-
- destination_path (String) : Absolute path of the destination file
|
86
|
-
|
87
|
-
Raises:
|
88
|
-
- FileCopyException : If file copy failed.
|
89
|
-
'''
|
90
|
-
|
91
|
-
local_dest = os.path.join(dest_dir, os.path.basename(source))
|
92
|
-
|
93
|
-
# Only attempt to copy if the target dir and source dir are different
|
94
|
-
if os.path.dirname(source) != dest_dir:
|
95
|
-
try:
|
96
|
-
shutil.copyfile(source, local_dest)
|
97
|
-
os.chmod(local_dest, 0o700)
|
98
|
-
|
99
|
-
except OSError as e:
|
100
|
-
raise FileCopyException(e, self.hostname)
|
101
|
-
|
102
|
-
else:
|
103
|
-
os.chmod(local_dest, 0o700)
|
104
|
-
|
105
|
-
return local_dest
|
106
|
-
|
107
|
-
def pull_file(self, remote_source, local_dir):
|
108
|
-
return self.push_file(remote_source, local_dir)
|
109
|
-
|
110
|
-
def close(self):
|
111
|
-
''' There's nothing to close here, and this really doesn't do anything
|
112
|
-
|
113
|
-
Returns:
|
114
|
-
- False, because it really did not "close" this channel.
|
115
|
-
'''
|
116
|
-
return False
|
117
|
-
|
118
|
-
def isdir(self, path):
|
119
|
-
"""Return true if the path refers to an existing directory.
|
120
|
-
|
121
|
-
Parameters
|
122
|
-
----------
|
123
|
-
path : str
|
124
|
-
Path of directory to check.
|
125
|
-
"""
|
126
|
-
|
127
|
-
return os.path.isdir(path)
|
128
|
-
|
129
|
-
def makedirs(self, path, mode=0o700, exist_ok=False):
|
130
|
-
"""Create a directory.
|
131
|
-
|
132
|
-
If intermediate directories do not exist, they will be created.
|
133
|
-
|
134
|
-
Parameters
|
135
|
-
----------
|
136
|
-
path : str
|
137
|
-
Path of directory to create.
|
138
|
-
mode : int
|
139
|
-
Permissions (posix-style) for the newly-created directory.
|
140
|
-
exist_ok : bool
|
141
|
-
If False, raise an OSError if the target directory already exists.
|
142
|
-
"""
|
143
|
-
|
144
|
-
return os.makedirs(path, mode, exist_ok)
|
145
|
-
|
146
|
-
def abspath(self, path):
|
147
|
-
"""Return the absolute path.
|
148
|
-
|
149
|
-
Parameters
|
150
|
-
----------
|
151
|
-
path : str
|
152
|
-
Path for which the absolute path will be returned.
|
153
|
-
"""
|
154
|
-
return os.path.abspath(path)
|
155
|
-
|
156
|
-
@property
|
157
|
-
def script_dir(self):
|
158
|
-
return self._script_dir
|
159
|
-
|
160
|
-
@script_dir.setter
|
161
|
-
def script_dir(self, value):
|
162
|
-
if value is not None:
|
163
|
-
value = self.abspath(value)
|
164
|
-
self._script_dir = value
|
@@ -1,110 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
import paramiko
|
3
|
-
import socket
|
4
|
-
|
5
|
-
from parsl.errors import OptionalModuleMissing
|
6
|
-
from parsl.channels.ssh.ssh import SSHChannel
|
7
|
-
|
8
|
-
try:
|
9
|
-
from oauth_ssh.ssh_service import SSHService
|
10
|
-
from oauth_ssh.oauth_ssh_token import find_access_token
|
11
|
-
_oauth_ssh_enabled = True
|
12
|
-
except (ImportError, NameError):
|
13
|
-
_oauth_ssh_enabled = False
|
14
|
-
|
15
|
-
|
16
|
-
logger = logging.getLogger(__name__)
|
17
|
-
|
18
|
-
|
19
|
-
class OAuthSSHChannel(SSHChannel):
|
20
|
-
"""SSH persistent channel. This enables remote execution on sites
|
21
|
-
accessible via ssh. This channel uses Globus based OAuth tokens for authentication.
|
22
|
-
"""
|
23
|
-
|
24
|
-
def __init__(self, hostname, username=None, script_dir=None, envs=None, port=22):
|
25
|
-
''' Initialize a persistent connection to the remote system.
|
26
|
-
We should know at this point whether ssh connectivity is possible
|
27
|
-
|
28
|
-
Args:
|
29
|
-
- hostname (String) : Hostname
|
30
|
-
|
31
|
-
KWargs:
|
32
|
-
- username (string) : Username on remote system
|
33
|
-
- script_dir (string) : Full path to a script dir where
|
34
|
-
generated scripts could be sent to.
|
35
|
-
- envs (dict) : A dictionary of env variables to be set when executing commands
|
36
|
-
- port (int) : Port at which the SSHService is running
|
37
|
-
|
38
|
-
Raises:
|
39
|
-
'''
|
40
|
-
if not _oauth_ssh_enabled:
|
41
|
-
raise OptionalModuleMissing(['oauth_ssh'],
|
42
|
-
"OauthSSHChannel requires oauth_ssh module and config.")
|
43
|
-
|
44
|
-
self.hostname = hostname
|
45
|
-
self.username = username
|
46
|
-
self.script_dir = script_dir
|
47
|
-
self.port = port
|
48
|
-
self.envs = {}
|
49
|
-
if envs is not None:
|
50
|
-
self.envs = envs
|
51
|
-
|
52
|
-
try:
|
53
|
-
access_token = find_access_token(hostname)
|
54
|
-
except Exception:
|
55
|
-
logger.exception("Failed to find the access token for {}".format(hostname))
|
56
|
-
raise
|
57
|
-
|
58
|
-
try:
|
59
|
-
self.service = SSHService(hostname, port)
|
60
|
-
self.transport = self.service.login(access_token, username)
|
61
|
-
|
62
|
-
except Exception:
|
63
|
-
logger.exception("Caught an exception in the OAuth authentication step with {}".format(hostname))
|
64
|
-
raise
|
65
|
-
|
66
|
-
self.sftp_client = paramiko.SFTPClient.from_transport(self.transport)
|
67
|
-
|
68
|
-
def execute_wait(self, cmd, walltime=60, envs={}):
|
69
|
-
''' Synchronously execute a commandline string on the shell.
|
70
|
-
|
71
|
-
This command does *NOT* honor walltime currently.
|
72
|
-
|
73
|
-
Args:
|
74
|
-
- cmd (string) : Commandline string to execute
|
75
|
-
|
76
|
-
Kwargs:
|
77
|
-
- walltime (int) : walltime in seconds
|
78
|
-
- envs (dict) : Dictionary of env variables
|
79
|
-
|
80
|
-
Returns:
|
81
|
-
- retcode : Return code from the execution, -1 on fail
|
82
|
-
- stdout : stdout string
|
83
|
-
- stderr : stderr string
|
84
|
-
|
85
|
-
Raises:
|
86
|
-
None.
|
87
|
-
'''
|
88
|
-
|
89
|
-
session = self.transport.open_session()
|
90
|
-
session.setblocking(0)
|
91
|
-
|
92
|
-
nbytes = 1024
|
93
|
-
session.exec_command(self.prepend_envs(cmd, envs))
|
94
|
-
session.settimeout(walltime)
|
95
|
-
|
96
|
-
try:
|
97
|
-
# Wait until command is executed
|
98
|
-
exit_status = session.recv_exit_status()
|
99
|
-
|
100
|
-
stdout = session.recv(nbytes).decode('utf-8')
|
101
|
-
stderr = session.recv_stderr(nbytes).decode('utf-8')
|
102
|
-
|
103
|
-
except socket.timeout:
|
104
|
-
logger.exception("Command failed to execute without timeout limit on {}".format(self))
|
105
|
-
raise
|
106
|
-
|
107
|
-
return exit_status, stdout, stderr
|
108
|
-
|
109
|
-
def close(self):
|
110
|
-
return self.transport.close()
|
parsl/channels/ssh/ssh.py
DELETED
@@ -1,276 +0,0 @@
|
|
1
|
-
import errno
|
2
|
-
import logging
|
3
|
-
import os
|
4
|
-
|
5
|
-
import paramiko
|
6
|
-
from parsl.channels.base import Channel
|
7
|
-
from parsl.channels.errors import BadHostKeyException, AuthException, SSHException, BadScriptPath, BadPermsScriptPath, FileCopyException
|
8
|
-
from parsl.utils import RepresentationMixin
|
9
|
-
|
10
|
-
logger = logging.getLogger(__name__)
|
11
|
-
|
12
|
-
|
13
|
-
class NoAuthSSHClient(paramiko.SSHClient):
|
14
|
-
def _auth(self, username, *args):
|
15
|
-
self._transport.auth_none(username)
|
16
|
-
return
|
17
|
-
|
18
|
-
|
19
|
-
class SSHChannel(Channel, RepresentationMixin):
|
20
|
-
''' SSH persistent channel. This enables remote execution on sites
|
21
|
-
accessible via ssh. It is assumed that the user has setup host keys
|
22
|
-
so as to ssh to the remote host. Which goes to say that the following
|
23
|
-
test on the commandline should work:
|
24
|
-
|
25
|
-
>>> ssh <username>@<hostname>
|
26
|
-
|
27
|
-
'''
|
28
|
-
|
29
|
-
def __init__(self, hostname, username=None, password=None, script_dir=None, envs=None,
|
30
|
-
gssapi_auth=False, skip_auth=False, port=22, key_filename=None, host_keys_filename=None):
|
31
|
-
''' Initialize a persistent connection to the remote system.
|
32
|
-
We should know at this point whether ssh connectivity is possible
|
33
|
-
|
34
|
-
Args:
|
35
|
-
- hostname (String) : Hostname
|
36
|
-
|
37
|
-
KWargs:
|
38
|
-
- username (string) : Username on remote system
|
39
|
-
- password (string) : Password for remote system
|
40
|
-
- port : The port designated for the ssh connection. Default is 22.
|
41
|
-
- script_dir (string) : Full path to a script dir where
|
42
|
-
generated scripts could be sent to.
|
43
|
-
- envs (dict) : A dictionary of environment variables to be set when executing commands
|
44
|
-
- key_filename (string or list): the filename, or list of filenames, of optional private key(s)
|
45
|
-
|
46
|
-
Raises:
|
47
|
-
'''
|
48
|
-
|
49
|
-
self.hostname = hostname
|
50
|
-
self.username = username
|
51
|
-
self.password = password
|
52
|
-
self.port = port
|
53
|
-
self.script_dir = script_dir
|
54
|
-
self.skip_auth = skip_auth
|
55
|
-
self.gssapi_auth = gssapi_auth
|
56
|
-
self.key_filename = key_filename
|
57
|
-
self.host_keys_filename = host_keys_filename
|
58
|
-
|
59
|
-
if self.skip_auth:
|
60
|
-
self.ssh_client = NoAuthSSHClient()
|
61
|
-
else:
|
62
|
-
self.ssh_client = paramiko.SSHClient()
|
63
|
-
self.ssh_client.load_system_host_keys(filename=host_keys_filename)
|
64
|
-
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
65
|
-
self.sftp_client = None
|
66
|
-
|
67
|
-
self.envs = {}
|
68
|
-
if envs is not None:
|
69
|
-
self.envs = envs
|
70
|
-
|
71
|
-
def _is_connected(self):
|
72
|
-
transport = self.ssh_client.get_transport() if self.ssh_client else None
|
73
|
-
return transport and transport.is_active()
|
74
|
-
|
75
|
-
def _connect(self):
|
76
|
-
if not self._is_connected():
|
77
|
-
logger.debug(f"connecting to {self.hostname}:{self.port}")
|
78
|
-
try:
|
79
|
-
self.ssh_client.connect(
|
80
|
-
self.hostname,
|
81
|
-
username=self.username,
|
82
|
-
password=self.password,
|
83
|
-
port=self.port,
|
84
|
-
allow_agent=True,
|
85
|
-
gss_auth=self.gssapi_auth,
|
86
|
-
gss_kex=self.gssapi_auth,
|
87
|
-
key_filename=self.key_filename
|
88
|
-
)
|
89
|
-
transport = self.ssh_client.get_transport()
|
90
|
-
self.sftp_client = paramiko.SFTPClient.from_transport(transport)
|
91
|
-
|
92
|
-
except paramiko.BadHostKeyException as e:
|
93
|
-
raise BadHostKeyException(e, self.hostname)
|
94
|
-
|
95
|
-
except paramiko.AuthenticationException as e:
|
96
|
-
raise AuthException(e, self.hostname)
|
97
|
-
|
98
|
-
except paramiko.SSHException as e:
|
99
|
-
raise SSHException(e, self.hostname)
|
100
|
-
|
101
|
-
except Exception as e:
|
102
|
-
raise SSHException(e, self.hostname)
|
103
|
-
|
104
|
-
def _valid_sftp_client(self):
|
105
|
-
self._connect()
|
106
|
-
return self.sftp_client
|
107
|
-
|
108
|
-
def _valid_ssh_client(self):
|
109
|
-
self._connect()
|
110
|
-
return self.ssh_client
|
111
|
-
|
112
|
-
def prepend_envs(self, cmd, env={}):
|
113
|
-
env.update(self.envs)
|
114
|
-
|
115
|
-
if len(env.keys()) > 0:
|
116
|
-
env_vars = ' '.join(['{}={}'.format(key, value) for key, value in env.items()])
|
117
|
-
return 'env {0} {1}'.format(env_vars, cmd)
|
118
|
-
return cmd
|
119
|
-
|
120
|
-
def execute_wait(self, cmd, walltime=2, envs={}):
|
121
|
-
''' Synchronously execute a commandline string on the shell.
|
122
|
-
|
123
|
-
Args:
|
124
|
-
- cmd (string) : Commandline string to execute
|
125
|
-
- walltime (int) : walltime in seconds
|
126
|
-
|
127
|
-
Kwargs:
|
128
|
-
- envs (dict) : Dictionary of env variables
|
129
|
-
|
130
|
-
Returns:
|
131
|
-
- retcode : Return code from the execution, -1 on fail
|
132
|
-
- stdout : stdout string
|
133
|
-
- stderr : stderr string
|
134
|
-
|
135
|
-
Raises:
|
136
|
-
None.
|
137
|
-
'''
|
138
|
-
|
139
|
-
# Execute the command
|
140
|
-
stdin, stdout, stderr = self._valid_ssh_client().exec_command(
|
141
|
-
self.prepend_envs(cmd, envs), bufsize=-1, timeout=walltime
|
142
|
-
)
|
143
|
-
# Block on exit status from the command
|
144
|
-
exit_status = stdout.channel.recv_exit_status()
|
145
|
-
return exit_status, stdout.read().decode("utf-8"), stderr.read().decode("utf-8")
|
146
|
-
|
147
|
-
def push_file(self, local_source, remote_dir):
|
148
|
-
''' Transport a local file to a directory on a remote machine
|
149
|
-
|
150
|
-
Args:
|
151
|
-
- local_source (string): Path
|
152
|
-
- remote_dir (string): Remote path
|
153
|
-
|
154
|
-
Returns:
|
155
|
-
- str: Path to copied file on remote machine
|
156
|
-
|
157
|
-
Raises:
|
158
|
-
- BadScriptPath : if script path on the remote side is bad
|
159
|
-
- BadPermsScriptPath : You do not have perms to make the channel script dir
|
160
|
-
- FileCopyException : FileCopy failed.
|
161
|
-
|
162
|
-
'''
|
163
|
-
remote_dest = os.path.join(remote_dir, os.path.basename(local_source))
|
164
|
-
|
165
|
-
try:
|
166
|
-
self.makedirs(remote_dir, exist_ok=True)
|
167
|
-
except IOError as e:
|
168
|
-
logger.exception("Pushing {0} to {1} failed".format(local_source, remote_dir))
|
169
|
-
if e.errno == 2:
|
170
|
-
raise BadScriptPath(e, self.hostname)
|
171
|
-
elif e.errno == 13:
|
172
|
-
raise BadPermsScriptPath(e, self.hostname)
|
173
|
-
else:
|
174
|
-
logger.exception("File push failed due to SFTP client failure")
|
175
|
-
raise FileCopyException(e, self.hostname)
|
176
|
-
try:
|
177
|
-
self._valid_sftp_client().put(local_source, remote_dest, confirm=True)
|
178
|
-
# Set perm because some systems require the script to be executable
|
179
|
-
self._valid_sftp_client().chmod(remote_dest, 0o700)
|
180
|
-
except Exception as e:
|
181
|
-
logger.exception("File push from local source {} to remote destination {} failed".format(
|
182
|
-
local_source, remote_dest))
|
183
|
-
raise FileCopyException(e, self.hostname)
|
184
|
-
|
185
|
-
return remote_dest
|
186
|
-
|
187
|
-
def pull_file(self, remote_source, local_dir):
|
188
|
-
''' Transport file on the remote side to a local directory
|
189
|
-
|
190
|
-
Args:
|
191
|
-
- remote_source (string): remote_source
|
192
|
-
- local_dir (string): Local directory to copy to
|
193
|
-
|
194
|
-
|
195
|
-
Returns:
|
196
|
-
- str: Local path to file
|
197
|
-
|
198
|
-
Raises:
|
199
|
-
- FileExists : Name collision at local directory.
|
200
|
-
- FileCopyException : FileCopy failed.
|
201
|
-
'''
|
202
|
-
|
203
|
-
local_dest = os.path.join(local_dir, os.path.basename(remote_source))
|
204
|
-
|
205
|
-
try:
|
206
|
-
os.makedirs(local_dir)
|
207
|
-
except OSError as e:
|
208
|
-
if e.errno != errno.EEXIST:
|
209
|
-
logger.exception("Failed to create local_dir: {0}".format(local_dir))
|
210
|
-
raise BadScriptPath(e, self.hostname)
|
211
|
-
|
212
|
-
try:
|
213
|
-
self._valid_sftp_client().get(remote_source, local_dest)
|
214
|
-
except Exception as e:
|
215
|
-
logger.exception("File pull failed")
|
216
|
-
raise FileCopyException(e, self.hostname)
|
217
|
-
|
218
|
-
return local_dest
|
219
|
-
|
220
|
-
def close(self):
|
221
|
-
if self._is_connected():
|
222
|
-
return self.ssh_client.close()
|
223
|
-
|
224
|
-
def isdir(self, path):
|
225
|
-
"""Return true if the path refers to an existing directory.
|
226
|
-
|
227
|
-
Parameters
|
228
|
-
----------
|
229
|
-
path : str
|
230
|
-
Path of directory on the remote side to check.
|
231
|
-
"""
|
232
|
-
result = True
|
233
|
-
try:
|
234
|
-
self._valid_sftp_client().lstat(path)
|
235
|
-
except FileNotFoundError:
|
236
|
-
result = False
|
237
|
-
|
238
|
-
return result
|
239
|
-
|
240
|
-
def makedirs(self, path, mode=0o700, exist_ok=False):
|
241
|
-
"""Create a directory on the remote side.
|
242
|
-
|
243
|
-
If intermediate directories do not exist, they will be created.
|
244
|
-
|
245
|
-
Parameters
|
246
|
-
----------
|
247
|
-
path : str
|
248
|
-
Path of directory on the remote side to create.
|
249
|
-
mode : int
|
250
|
-
Permissions (posix-style) for the newly-created directory.
|
251
|
-
exist_ok : bool
|
252
|
-
If False, raise an OSError if the target directory already exists.
|
253
|
-
"""
|
254
|
-
if exist_ok is False and self.isdir(path):
|
255
|
-
raise OSError('Target directory {} already exists'.format(path))
|
256
|
-
|
257
|
-
self.execute_wait('mkdir -p {}'.format(path))
|
258
|
-
self._valid_sftp_client().chmod(path, mode)
|
259
|
-
|
260
|
-
def abspath(self, path):
|
261
|
-
"""Return the absolute path on the remote side.
|
262
|
-
|
263
|
-
Parameters
|
264
|
-
----------
|
265
|
-
path : str
|
266
|
-
Path for which the absolute path will be returned.
|
267
|
-
"""
|
268
|
-
return self._valid_sftp_client().normalize(path)
|
269
|
-
|
270
|
-
@property
|
271
|
-
def script_dir(self):
|
272
|
-
return self._script_dir
|
273
|
-
|
274
|
-
@script_dir.setter
|
275
|
-
def script_dir(self, value):
|
276
|
-
self._script_dir = value
|
File without changes
|