toil 8.1.0b1__py3-none-any.whl → 9.0.0__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.
- toil/__init__.py +0 -35
- toil/batchSystems/abstractBatchSystem.py +1 -1
- toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
- toil/batchSystems/awsBatch.py +1 -1
- toil/batchSystems/cleanup_support.py +1 -1
- toil/batchSystems/kubernetes.py +53 -7
- toil/batchSystems/local_support.py +1 -1
- toil/batchSystems/mesos/batchSystem.py +13 -8
- toil/batchSystems/mesos/test/__init__.py +3 -2
- toil/batchSystems/registry.py +15 -118
- toil/batchSystems/singleMachine.py +1 -1
- toil/batchSystems/slurm.py +27 -26
- toil/bus.py +5 -3
- toil/common.py +59 -12
- toil/cwl/cwltoil.py +81 -38
- toil/cwl/utils.py +103 -3
- toil/job.py +64 -49
- toil/jobStores/abstractJobStore.py +35 -239
- toil/jobStores/aws/jobStore.py +2 -1
- toil/jobStores/fileJobStore.py +27 -2
- toil/jobStores/googleJobStore.py +110 -33
- toil/leader.py +9 -0
- toil/lib/accelerators.py +4 -2
- toil/lib/aws/utils.py.orig +504 -0
- toil/lib/bioio.py +1 -1
- toil/lib/docker.py +252 -91
- toil/lib/dockstore.py +11 -3
- toil/lib/exceptions.py +5 -3
- toil/lib/generatedEC2Lists.py +81 -19
- toil/lib/history.py +87 -13
- toil/lib/history_submission.py +23 -9
- toil/lib/io.py +34 -22
- toil/lib/misc.py +8 -2
- toil/lib/plugins.py +106 -0
- toil/lib/resources.py +2 -1
- toil/lib/threading.py +11 -10
- toil/lib/url.py +320 -0
- toil/options/common.py +8 -0
- toil/options/cwl.py +13 -1
- toil/options/runner.py +17 -10
- toil/options/wdl.py +22 -0
- toil/provisioners/aws/awsProvisioner.py +25 -2
- toil/server/api_spec/LICENSE +201 -0
- toil/server/api_spec/README.rst +5 -0
- toil/server/app.py +12 -6
- toil/server/cli/wes_cwl_runner.py +3 -2
- toil/server/wes/abstract_backend.py +21 -43
- toil/server/wes/toil_backend.py +2 -2
- toil/test/__init__.py +275 -115
- toil/test/batchSystems/batchSystemTest.py +228 -213
- toil/test/batchSystems/batch_system_plugin_test.py +7 -0
- toil/test/batchSystems/test_slurm.py +27 -0
- toil/test/cactus/pestis.tar.gz +0 -0
- toil/test/conftest.py +7 -0
- toil/test/cwl/2.fasta +11 -0
- toil/test/cwl/2.fastq +12 -0
- toil/test/cwl/conftest.py +1 -1
- toil/test/cwl/cwlTest.py +1175 -870
- toil/test/cwl/directory/directory/file.txt +15 -0
- toil/test/cwl/download_directory_file.json +4 -0
- toil/test/cwl/download_directory_s3.json +4 -0
- toil/test/cwl/download_file.json +6 -0
- toil/test/cwl/download_http.json +6 -0
- toil/test/cwl/download_https.json +6 -0
- toil/test/cwl/download_s3.json +6 -0
- toil/test/cwl/download_subdirectory_file.json +5 -0
- toil/test/cwl/download_subdirectory_s3.json +5 -0
- toil/test/cwl/empty.json +1 -0
- toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
- toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
- toil/test/cwl/optional-file-exists.json +6 -0
- toil/test/cwl/optional-file-missing.json +6 -0
- toil/test/cwl/preemptible_expression.json +1 -0
- toil/test/cwl/revsort-job-missing.json +6 -0
- toil/test/cwl/revsort-job.json +6 -0
- toil/test/cwl/s3_secondary_file.json +16 -0
- toil/test/cwl/seqtk_seq_job.json +6 -0
- toil/test/cwl/stream.json +6 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
- toil/test/cwl/whale.txt +16 -0
- toil/test/docs/scripts/example_alwaysfail.py +38 -0
- toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
- toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
- toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
- toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
- toil/test/docs/scripts/tutorial_arguments.py +23 -0
- toil/test/docs/scripts/tutorial_debugging.patch +12 -0
- toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
- toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
- toil/test/docs/scripts/tutorial_docker.py +20 -0
- toil/test/docs/scripts/tutorial_dynamic.py +24 -0
- toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
- toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
- toil/test/docs/scripts/tutorial_helloworld.py +15 -0
- toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
- toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
- toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
- toil/test/docs/scripts/tutorial_managing.py +29 -0
- toil/test/docs/scripts/tutorial_managing2.py +56 -0
- toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
- toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
- toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
- toil/test/docs/scripts/tutorial_promises.py +25 -0
- toil/test/docs/scripts/tutorial_promises2.py +30 -0
- toil/test/docs/scripts/tutorial_quickstart.py +22 -0
- toil/test/docs/scripts/tutorial_requirements.py +44 -0
- toil/test/docs/scripts/tutorial_services.py +45 -0
- toil/test/docs/scripts/tutorial_staging.py +45 -0
- toil/test/docs/scripts/tutorial_stats.py +64 -0
- toil/test/docs/scriptsTest.py +2 -1
- toil/test/lib/aws/test_iam.py +3 -1
- toil/test/lib/dockerTest.py +205 -122
- toil/test/lib/test_history.py +101 -77
- toil/test/lib/test_url.py +69 -0
- toil/test/lib/url_plugin_test.py +105 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +13 -10
- toil/test/provisioners/clusterTest.py +17 -4
- toil/test/provisioners/gceProvisionerTest.py +17 -15
- toil/test/server/serverTest.py +78 -36
- toil/test/sort/sort.py +4 -1
- toil/test/src/busTest.py +17 -17
- toil/test/src/deferredFunctionTest.py +145 -132
- toil/test/src/importExportFileTest.py +71 -63
- toil/test/src/jobEncapsulationTest.py +27 -28
- toil/test/src/jobServiceTest.py +149 -133
- toil/test/src/jobTest.py +219 -211
- toil/test/src/miscTests.py +66 -60
- toil/test/src/promisedRequirementTest.py +163 -169
- toil/test/src/regularLogTest.py +24 -24
- toil/test/src/resourceTest.py +82 -76
- toil/test/src/restartDAGTest.py +51 -47
- toil/test/src/resumabilityTest.py +24 -19
- toil/test/src/retainTempDirTest.py +60 -57
- toil/test/src/systemTest.py +17 -13
- toil/test/src/threadingTest.py +29 -32
- toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
- toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
- toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
- toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
- toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
- toil/test/utils/toilDebugTest.py +117 -102
- toil/test/utils/toilKillTest.py +54 -53
- toil/test/utils/utilsTest.py +303 -229
- toil/test/wdl/lint_error.wdl +9 -0
- toil/test/wdl/md5sum/empty_file.json +1 -0
- toil/test/wdl/md5sum/md5sum-gs.json +1 -0
- toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
- toil/test/wdl/md5sum/md5sum.input +1 -0
- toil/test/wdl/md5sum/md5sum.json +1 -0
- toil/test/wdl/md5sum/md5sum.wdl +25 -0
- toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
- toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
- toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
- toil/test/wdl/standard_library/as_map.json +16 -0
- toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
- toil/test/wdl/standard_library/as_pairs.json +7 -0
- toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
- toil/test/wdl/standard_library/ceil.json +3 -0
- toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
- toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
- toil/test/wdl/standard_library/collect_by_key.json +1 -0
- toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
- toil/test/wdl/standard_library/cross.json +11 -0
- toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
- toil/test/wdl/standard_library/flatten.json +7 -0
- toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
- toil/test/wdl/standard_library/floor.json +3 -0
- toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
- toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
- toil/test/wdl/standard_library/keys.json +8 -0
- toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
- toil/test/wdl/standard_library/length.json +7 -0
- toil/test/wdl/standard_library/length_as_input.wdl +16 -0
- toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
- toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
- toil/test/wdl/standard_library/length_invalid.json +3 -0
- toil/test/wdl/standard_library/range.json +3 -0
- toil/test/wdl/standard_library/range_0.json +3 -0
- toil/test/wdl/standard_library/range_as_input.wdl +17 -0
- toil/test/wdl/standard_library/range_invalid.json +3 -0
- toil/test/wdl/standard_library/read_boolean.json +3 -0
- toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_float.json +3 -0
- toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_int.json +3 -0
- toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_json.json +3 -0
- toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_lines.json +3 -0
- toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_map.json +3 -0
- toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_string.json +3 -0
- toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_tsv.json +3 -0
- toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
- toil/test/wdl/standard_library/round.json +3 -0
- toil/test/wdl/standard_library/round_as_command.wdl +16 -0
- toil/test/wdl/standard_library/round_as_input.wdl +16 -0
- toil/test/wdl/standard_library/size.json +3 -0
- toil/test/wdl/standard_library/size_as_command.wdl +17 -0
- toil/test/wdl/standard_library/size_as_output.wdl +36 -0
- toil/test/wdl/standard_library/stderr.json +3 -0
- toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
- toil/test/wdl/standard_library/stdout.json +3 -0
- toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
- toil/test/wdl/standard_library/sub.json +3 -0
- toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
- toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
- toil/test/wdl/standard_library/transpose.json +6 -0
- toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
- toil/test/wdl/standard_library/write_json.json +6 -0
- toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_lines.json +7 -0
- toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_map.json +6 -0
- toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_tsv.json +6 -0
- toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
- toil/test/wdl/standard_library/zip.json +12 -0
- toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
- toil/test/wdl/test.csv +3 -0
- toil/test/wdl/test.tsv +3 -0
- toil/test/wdl/testfiles/croo.wdl +38 -0
- toil/test/wdl/testfiles/drop_files.wdl +62 -0
- toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
- toil/test/wdl/testfiles/empty.txt +0 -0
- toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
- toil/test/wdl/testfiles/random.wdl +66 -0
- toil/test/wdl/testfiles/read_file.wdl +18 -0
- toil/test/wdl/testfiles/string_file_coercion.json +1 -0
- toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
- toil/test/wdl/testfiles/test.json +4 -0
- toil/test/wdl/testfiles/test_boolean.txt +1 -0
- toil/test/wdl/testfiles/test_float.txt +1 -0
- toil/test/wdl/testfiles/test_int.txt +1 -0
- toil/test/wdl/testfiles/test_lines.txt +5 -0
- toil/test/wdl/testfiles/test_map.txt +2 -0
- toil/test/wdl/testfiles/test_string.txt +1 -0
- toil/test/wdl/testfiles/url_to_file.wdl +13 -0
- toil/test/wdl/testfiles/url_to_optional_file.wdl +14 -0
- toil/test/wdl/testfiles/vocab.json +1 -0
- toil/test/wdl/testfiles/vocab.wdl +66 -0
- toil/test/wdl/testfiles/wait.wdl +34 -0
- toil/test/wdl/wdl_specification/type_pair.json +23 -0
- toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
- toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
- toil/test/wdl/wdl_specification/v1_spec.json +1 -0
- toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
- toil/test/wdl/wdltoil_test.py +751 -529
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/utils/toilSshCluster.py +23 -0
- toil/utils/toilUpdateEC2Instances.py +1 -0
- toil/version.py +5 -5
- toil/wdl/wdltoil.py +518 -437
- toil/worker.py +11 -6
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/METADATA +25 -24
- toil-9.0.0.dist-info/RECORD +444 -0
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/WHEEL +1 -1
- toil-8.1.0b1.dist-info/RECORD +0 -259
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/entry_points.txt +0 -0
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info/licenses}/LICENSE +0 -0
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/top_level.txt +0 -0
toil/lib/threading.py
CHANGED
|
@@ -36,12 +36,13 @@ import psutil
|
|
|
36
36
|
|
|
37
37
|
from toil.lib.exceptions import raise_
|
|
38
38
|
from toil.lib.io import robust_rmtree
|
|
39
|
+
from toil.lib.misc import StrPath
|
|
39
40
|
|
|
40
41
|
logger = logging.getLogger(__name__)
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
def ensure_filesystem_lockable(
|
|
44
|
-
path:
|
|
45
|
+
path: StrPath, timeout: float = 30, hint: Optional[str] = None
|
|
45
46
|
) -> None:
|
|
46
47
|
"""
|
|
47
48
|
Make sure that the filesystem used at the given path is one where locks are safe to use.
|
|
@@ -71,7 +72,7 @@ def ensure_filesystem_lockable(
|
|
|
71
72
|
# Start a child process to stat the path. See <https://unix.stackexchange.com/a/402236>.
|
|
72
73
|
# We really should call statfs but no bindings for it are in PyPI.
|
|
73
74
|
completed = subprocess.run(
|
|
74
|
-
["stat", "-f", "-c", "%T", path],
|
|
75
|
+
["stat", "-f", "-c", "%T", str(path)],
|
|
75
76
|
check=True,
|
|
76
77
|
capture_output=True,
|
|
77
78
|
timeout=timeout,
|
|
@@ -85,7 +86,7 @@ def ensure_filesystem_lockable(
|
|
|
85
86
|
# Stat didn't work. Maybe we don't have the right version of stat installed?
|
|
86
87
|
logger.warning(
|
|
87
88
|
"Could not determine filesystem type at %s because of: %s",
|
|
88
|
-
path,
|
|
89
|
+
str(path),
|
|
89
90
|
e.stderr.decode("utf-8", errors="replace").strip(),
|
|
90
91
|
)
|
|
91
92
|
# If we don't know the filesystem type, keep going anyway.
|
|
@@ -107,7 +108,7 @@ def ensure_filesystem_lockable(
|
|
|
107
108
|
# flaky with regard to locks actually locking anything).
|
|
108
109
|
logger.debug(
|
|
109
110
|
"Detected that %s has lockable filesystem type: %s",
|
|
110
|
-
path,
|
|
111
|
+
str(path),
|
|
111
112
|
filesystem_type,
|
|
112
113
|
)
|
|
113
114
|
|
|
@@ -518,7 +519,7 @@ def process_name_exists(base_dir: str, name: str) -> bool:
|
|
|
518
519
|
# Similar to the process naming system above, we define a global mutex system
|
|
519
520
|
# for critical sections, based just around file locks.
|
|
520
521
|
@contextmanager
|
|
521
|
-
def global_mutex(base_dir:
|
|
522
|
+
def global_mutex(base_dir: StrPath, mutex: str) -> Iterator[None]:
|
|
522
523
|
"""
|
|
523
524
|
Context manager that locks a mutex. The mutex is identified by the given
|
|
524
525
|
name, and scoped to the given directory. Works across all containers that
|
|
@@ -527,7 +528,7 @@ def global_mutex(base_dir: str, mutex: str) -> Iterator[None]:
|
|
|
527
528
|
|
|
528
529
|
Only works between processes, NOT between threads.
|
|
529
530
|
|
|
530
|
-
:param
|
|
531
|
+
:param base_dir: Base directory to work in. Defines the shared namespace.
|
|
531
532
|
:param str mutex: Mutex to lock. Must be a permissible path component.
|
|
532
533
|
"""
|
|
533
534
|
|
|
@@ -674,7 +675,7 @@ class LastProcessStandingArena:
|
|
|
674
675
|
Consider using a try/finally; this class is not a context manager.
|
|
675
676
|
"""
|
|
676
677
|
|
|
677
|
-
def __init__(self, base_dir:
|
|
678
|
+
def __init__(self, base_dir: StrPath, name: str) -> None:
|
|
678
679
|
"""
|
|
679
680
|
Connect to the arena specified by the given base_dir and name.
|
|
680
681
|
|
|
@@ -683,12 +684,12 @@ class LastProcessStandingArena:
|
|
|
683
684
|
|
|
684
685
|
Doesn't enter or leave the arena.
|
|
685
686
|
|
|
686
|
-
:param
|
|
687
|
-
:param
|
|
687
|
+
:param base_dir: Base directory to work in. Defines the shared namespace.
|
|
688
|
+
:param name: Name of the arena. Must be a permissible path component.
|
|
688
689
|
"""
|
|
689
690
|
|
|
690
691
|
# Save the base_dir which namespaces everything
|
|
691
|
-
self.base_dir = base_dir
|
|
692
|
+
self.base_dir = str(base_dir)
|
|
692
693
|
|
|
693
694
|
# We need a mutex name to allow only one process to be entering or
|
|
694
695
|
# leaving at a time.
|
toil/lib/url.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Copyright (C) 2015-2025 Regents of the University of California
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
from abc import ABC, ABCMeta, abstractmethod
|
|
17
|
+
from typing import (
|
|
18
|
+
IO,
|
|
19
|
+
TYPE_CHECKING,
|
|
20
|
+
Any,
|
|
21
|
+
Callable,
|
|
22
|
+
ContextManager,
|
|
23
|
+
Literal,
|
|
24
|
+
Optional,
|
|
25
|
+
Union,
|
|
26
|
+
cast,
|
|
27
|
+
overload,
|
|
28
|
+
Type,
|
|
29
|
+
)
|
|
30
|
+
from urllib.parse import ParseResult, urlparse
|
|
31
|
+
|
|
32
|
+
from toil.lib.exceptions import UnimplementedURLException
|
|
33
|
+
from toil.lib.memoize import memoize
|
|
34
|
+
from toil.lib.plugins import register_plugin, get_plugin
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from botocore.exceptions import ProxyConnectionError
|
|
38
|
+
except ImportError:
|
|
39
|
+
|
|
40
|
+
class ProxyConnectionError(BaseException): # type: ignore
|
|
41
|
+
"""Dummy class."""
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
class URLAccess:
|
|
46
|
+
"""
|
|
47
|
+
Widget for accessing external storage (URLs).
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def url_exists(cls, src_uri: str) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Return True if the file at the given URI exists, and False otherwise.
|
|
54
|
+
|
|
55
|
+
May raise an error if file existence cannot be determined.
|
|
56
|
+
|
|
57
|
+
:param src_uri: URL that points to a file or object in the storage
|
|
58
|
+
mechanism of a supported URL scheme e.g. a blob in an AWS s3 bucket.
|
|
59
|
+
"""
|
|
60
|
+
parseResult = urlparse(src_uri)
|
|
61
|
+
otherCls = cls._find_url_implementation(parseResult)
|
|
62
|
+
return otherCls._url_exists(parseResult)
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def get_size(cls, src_uri: str) -> Optional[int]:
|
|
66
|
+
"""
|
|
67
|
+
Get the size in bytes of the file at the given URL, or None if it cannot be obtained.
|
|
68
|
+
|
|
69
|
+
:param src_uri: URL that points to a file or object in the storage
|
|
70
|
+
mechanism of a supported URL scheme e.g. a blob in an AWS s3 bucket.
|
|
71
|
+
"""
|
|
72
|
+
parseResult = urlparse(src_uri)
|
|
73
|
+
otherCls = cls._find_url_implementation(parseResult)
|
|
74
|
+
return otherCls._get_size(parseResult)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def get_is_directory(cls, src_uri: str) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Return True if the thing at the given URL is a directory, and False if
|
|
80
|
+
it is a file. The URL may or may not end in '/'.
|
|
81
|
+
"""
|
|
82
|
+
parseResult = urlparse(src_uri)
|
|
83
|
+
otherCls = cls._find_url_implementation(parseResult)
|
|
84
|
+
return otherCls._get_is_directory(parseResult)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def list_url(cls, src_uri: str) -> list[str]:
|
|
88
|
+
"""
|
|
89
|
+
List the directory at the given URL. Returned path components can be
|
|
90
|
+
joined with '/' onto the passed URL to form new URLs. Those that end in
|
|
91
|
+
'/' correspond to directories. The provided URL may or may not end with
|
|
92
|
+
'/'.
|
|
93
|
+
|
|
94
|
+
Currently supported schemes are:
|
|
95
|
+
|
|
96
|
+
- 's3' for objects in Amazon S3
|
|
97
|
+
e.g. s3://bucket/prefix/
|
|
98
|
+
|
|
99
|
+
- 'file' for local files
|
|
100
|
+
e.g. file:///local/dir/path/
|
|
101
|
+
|
|
102
|
+
:param str src_uri: URL that points to a directory or prefix in the storage mechanism of a
|
|
103
|
+
supported URL scheme e.g. a prefix in an AWS s3 bucket.
|
|
104
|
+
|
|
105
|
+
:return: A list of URL components in the given directory, already URL-encoded.
|
|
106
|
+
"""
|
|
107
|
+
parseResult = urlparse(src_uri)
|
|
108
|
+
otherCls = cls._find_url_implementation(parseResult)
|
|
109
|
+
return otherCls._list_url(parseResult)
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def read_from_url(cls, src_uri: str, writable: IO[bytes]) -> tuple[int, bool]:
|
|
113
|
+
"""
|
|
114
|
+
Read the given URL and write its content into the given writable stream.
|
|
115
|
+
|
|
116
|
+
Raises FileNotFoundError if the URL doesn't exist.
|
|
117
|
+
|
|
118
|
+
:return: The size of the file in bytes and whether the executable permission bit is set
|
|
119
|
+
"""
|
|
120
|
+
parseResult = urlparse(src_uri)
|
|
121
|
+
otherCls = cls._find_url_implementation(parseResult)
|
|
122
|
+
return otherCls._read_from_url(parseResult, writable)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def open_url(cls, src_uri: str) -> IO[bytes]:
|
|
126
|
+
"""
|
|
127
|
+
Read from the given URI.
|
|
128
|
+
|
|
129
|
+
Raises FileNotFoundError if the URL doesn't exist.
|
|
130
|
+
|
|
131
|
+
Has a readable stream interface, unlike :meth:`read_from_url` which
|
|
132
|
+
takes a writable stream.
|
|
133
|
+
"""
|
|
134
|
+
parseResult = urlparse(src_uri)
|
|
135
|
+
otherCls = cls._find_url_implementation(parseResult)
|
|
136
|
+
return otherCls._open_url(parseResult)
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def _url_exists(cls, url: ParseResult) -> bool:
|
|
141
|
+
"""
|
|
142
|
+
Return True if the item at the given URL exists, and Flase otherwise.
|
|
143
|
+
|
|
144
|
+
May raise an error if file existence cannot be determined.
|
|
145
|
+
"""
|
|
146
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def _get_size(cls, url: ParseResult) -> Optional[int]:
|
|
151
|
+
"""
|
|
152
|
+
Get the size of the object at the given URL, or None if it cannot be obtained.
|
|
153
|
+
"""
|
|
154
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
@abstractmethod
|
|
158
|
+
def _get_is_directory(cls, url: ParseResult) -> bool:
|
|
159
|
+
"""
|
|
160
|
+
Return True if the thing at the given URL is a directory, and False if
|
|
161
|
+
it is a file or it is known not to exist. The URL may or may not end in
|
|
162
|
+
'/'.
|
|
163
|
+
|
|
164
|
+
:param url: URL that points to a file or object, or directory or prefix,
|
|
165
|
+
in the storage mechanism of a supported URL scheme e.g. a blob
|
|
166
|
+
in an AWS s3 bucket.
|
|
167
|
+
"""
|
|
168
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def _read_from_url(cls, url: ParseResult, writable: IO[bytes]) -> tuple[int, bool]:
|
|
173
|
+
"""
|
|
174
|
+
Reads the contents of the object at the specified location and writes it to the given
|
|
175
|
+
writable stream.
|
|
176
|
+
|
|
177
|
+
Raises FileNotFoundError if the thing at the URL is not found.
|
|
178
|
+
|
|
179
|
+
:param ParseResult url: URL that points to a file or object in the storage
|
|
180
|
+
mechanism of a supported URL scheme e.g. a blob in an AWS s3 bucket.
|
|
181
|
+
|
|
182
|
+
:param IO[bytes] writable: a writable stream
|
|
183
|
+
|
|
184
|
+
:return: The size of the file in bytes and whether the executable permission bit is set
|
|
185
|
+
"""
|
|
186
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def _list_url(cls, url: ParseResult) -> list[str]:
|
|
191
|
+
"""
|
|
192
|
+
List the contents of the given URL, which may or may not end in '/'
|
|
193
|
+
|
|
194
|
+
Returns a list of URL components. Those that end in '/' are meant to be
|
|
195
|
+
directories, while those that do not are meant to be files.
|
|
196
|
+
|
|
197
|
+
:param ParseResult url: URL that points to a directory or prefix in the
|
|
198
|
+
storage mechanism of a supported URL scheme e.g. a prefix in an AWS s3
|
|
199
|
+
bucket.
|
|
200
|
+
|
|
201
|
+
:return: The children of the given URL, already URL-encoded if
|
|
202
|
+
appropriate. (If the URL is a bare path, no encoding is done.)
|
|
203
|
+
"""
|
|
204
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
@abstractmethod
|
|
208
|
+
def _open_url(cls, url: ParseResult) -> IO[bytes]:
|
|
209
|
+
"""
|
|
210
|
+
Get a stream of the object at the specified location.
|
|
211
|
+
|
|
212
|
+
Raises FileNotFoundError if the thing at the URL is not found.
|
|
213
|
+
"""
|
|
214
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
@abstractmethod
|
|
218
|
+
def _write_to_url(
|
|
219
|
+
cls,
|
|
220
|
+
readable: Union[IO[bytes], IO[str]],
|
|
221
|
+
url: ParseResult,
|
|
222
|
+
executable: bool = False,
|
|
223
|
+
) -> None:
|
|
224
|
+
"""
|
|
225
|
+
Reads the contents of the given readable stream and writes it to the object at the
|
|
226
|
+
specified location. Raises FileNotFoundError if the URL doesn't exist.
|
|
227
|
+
|
|
228
|
+
:param Union[IO[bytes], IO[str]] readable: a readable stream
|
|
229
|
+
|
|
230
|
+
:param ParseResult url: URL that points to a file or object in the storage
|
|
231
|
+
mechanism of a supported URL scheme e.g. a blob in an AWS s3 bucket.
|
|
232
|
+
|
|
233
|
+
:param bool executable: determines if the file has executable permissions
|
|
234
|
+
"""
|
|
235
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def _supports_url(cls, url: ParseResult, export: bool = False) -> bool:
|
|
240
|
+
"""
|
|
241
|
+
Returns True if the url access implementation supports the URL's scheme.
|
|
242
|
+
|
|
243
|
+
:param ParseResult url: a parsed URL that may be supported
|
|
244
|
+
|
|
245
|
+
:param bool export: Determines if the url is supported for exported
|
|
246
|
+
|
|
247
|
+
:return bool: returns true if the cls supports the URL
|
|
248
|
+
"""
|
|
249
|
+
raise NotImplementedError(f"No implementation for {url}")
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def _find_url_implementation(
|
|
253
|
+
cls, url: ParseResult, export: bool = False
|
|
254
|
+
) -> type["URLAccess"]:
|
|
255
|
+
"""
|
|
256
|
+
Returns the URLAccess subclass that supports the given URL.
|
|
257
|
+
|
|
258
|
+
:param ParseResult url: The given URL
|
|
259
|
+
|
|
260
|
+
:param bool export: Determines if the url is supported for exporting
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
implementation_factory = get_plugin("url_access", url.scheme.lower())
|
|
265
|
+
except KeyError:
|
|
266
|
+
raise UnimplementedURLException(url, "export" if export else "import")
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
implementation = cast(Type[URLAccess], implementation_factory())
|
|
270
|
+
except (ImportError, ProxyConnectionError):
|
|
271
|
+
logger.debug(
|
|
272
|
+
"Unable to import implementation for scheme '%s', as is expected if the corresponding extra was "
|
|
273
|
+
"omitted at installation time.",
|
|
274
|
+
url.scheme.lower(),
|
|
275
|
+
)
|
|
276
|
+
raise UnimplementedURLException(url, "export" if export else "import")
|
|
277
|
+
|
|
278
|
+
if implementation._supports_url(url, export):
|
|
279
|
+
return implementation
|
|
280
|
+
raise UnimplementedURLException(url, "export" if export else "import")
|
|
281
|
+
|
|
282
|
+
#####
|
|
283
|
+
# Built-in url access
|
|
284
|
+
#####
|
|
285
|
+
|
|
286
|
+
def file_job_store_factory() -> type[URLAccess]:
|
|
287
|
+
from toil.jobStores.fileJobStore import FileJobStore
|
|
288
|
+
|
|
289
|
+
return FileJobStore
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def google_job_store_factory() -> type[URLAccess]:
|
|
293
|
+
from toil.jobStores.googleJobStore import GoogleJobStore
|
|
294
|
+
|
|
295
|
+
return GoogleJobStore
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def aws_job_store_factory() -> type[URLAccess]:
|
|
299
|
+
from toil.jobStores.aws.jobStore import AWSJobStore
|
|
300
|
+
|
|
301
|
+
return AWSJobStore
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def job_store_support_factory() -> type[URLAccess]:
|
|
305
|
+
from toil.jobStores.abstractJobStore import JobStoreSupport
|
|
306
|
+
|
|
307
|
+
return JobStoreSupport
|
|
308
|
+
|
|
309
|
+
#make sure my py still works and the tests work
|
|
310
|
+
# can then get rid of _url_access_classes method
|
|
311
|
+
|
|
312
|
+
#####
|
|
313
|
+
# Registers all built-in urls
|
|
314
|
+
#####
|
|
315
|
+
register_plugin("url_access", "file", file_job_store_factory)
|
|
316
|
+
register_plugin("url_access", "gs", google_job_store_factory)
|
|
317
|
+
register_plugin("url_access", "s3", aws_job_store_factory)
|
|
318
|
+
register_plugin("url_access", "http", job_store_support_factory)
|
|
319
|
+
register_plugin("url_access", "https", job_store_support_factory)
|
|
320
|
+
register_plugin("url_access", "ftp", job_store_support_factory)
|
toil/options/common.py
CHANGED
|
@@ -860,6 +860,14 @@ def add_base_toil_options(
|
|
|
860
860
|
help=f"Number of times to retry a failing job before giving up and "
|
|
861
861
|
f"labeling job failed. default={1}",
|
|
862
862
|
)
|
|
863
|
+
job_options.add_argument(
|
|
864
|
+
"--stopOnFirstFailure",
|
|
865
|
+
dest="stop_on_first_failure",
|
|
866
|
+
type=strtobool,
|
|
867
|
+
default=False,
|
|
868
|
+
metavar="BOOL",
|
|
869
|
+
help="Stop the workflow at the first complete job failure.",
|
|
870
|
+
)
|
|
863
871
|
job_options.add_argument(
|
|
864
872
|
"--enableUnlimitedPreemptibleRetries",
|
|
865
873
|
"--enableUnlimitedPreemptableRetries",
|
toil/options/cwl.py
CHANGED
|
@@ -3,6 +3,8 @@ from argparse import ArgumentParser
|
|
|
3
3
|
|
|
4
4
|
from configargparse import SUPPRESS
|
|
5
5
|
|
|
6
|
+
from toil.lib.conversions import human2bytes
|
|
7
|
+
from toil.options.common import make_open_interval_action
|
|
6
8
|
from toil.version import baseVersion
|
|
7
9
|
|
|
8
10
|
|
|
@@ -411,9 +413,19 @@ def add_cwl_options(parser: ArgumentParser, suppress: bool = True) -> None:
|
|
|
411
413
|
"--no-cwl-default-ram",
|
|
412
414
|
action="store_false",
|
|
413
415
|
help=suppress_help
|
|
414
|
-
or "Do not apply CWL specification default ramMin, so that Toil --defaultMemory applies.",
|
|
416
|
+
or "Do not apply CWL specification default ramMin, so that Toil --defaultMemory applies. This can help jobs get to Slurm with no memory limit assigned.",
|
|
415
417
|
dest="cwl_default_ram",
|
|
416
418
|
)
|
|
419
|
+
parser.add_argument(
|
|
420
|
+
"--cwl-min-ram",
|
|
421
|
+
type=human2bytes,
|
|
422
|
+
action=make_open_interval_action(1),
|
|
423
|
+
help=suppress_help
|
|
424
|
+
or "Specify a minimum memory allocation for all tasks ."
|
|
425
|
+
"If --no-cwl-default-ram is passed, this does not apply to tools that do not "
|
|
426
|
+
"specify a memory requirement; --defaultMemory is used for those tools"
|
|
427
|
+
"in that case."
|
|
428
|
+
)
|
|
417
429
|
parser.add_argument(
|
|
418
430
|
"--destBucket",
|
|
419
431
|
type=str,
|
toil/options/runner.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from argparse import ArgumentParser
|
|
1
|
+
from argparse import ArgumentParser, SUPPRESS
|
|
2
2
|
|
|
3
3
|
from toil.lib.conversions import human2bytes
|
|
4
4
|
|
|
@@ -25,17 +25,23 @@ def add_runner_options(
|
|
|
25
25
|
help="Run the file imports on a worker instead of the leader. This is useful if the leader is not optimized for high network performance. "
|
|
26
26
|
"If set to true, the argument --importWorkersDisk must also be set."
|
|
27
27
|
)
|
|
28
|
-
|
|
28
|
+
import_workers_batchsize_argument = ["--importWorkersBatchSize"]
|
|
29
29
|
if cwl:
|
|
30
|
-
|
|
30
|
+
import_workers_batchsize_argument.append("--import-workers-batch-size")
|
|
31
31
|
parser.add_argument(
|
|
32
|
-
*
|
|
33
|
-
dest="
|
|
32
|
+
*import_workers_batchsize_argument,
|
|
33
|
+
dest="import_workers_batchsize",
|
|
34
34
|
type=lambda x: human2bytes(str(x)),
|
|
35
35
|
default="1 GiB",
|
|
36
|
-
help="Specify the file size
|
|
37
|
-
|
|
36
|
+
help="Specify the target total file size for file import batches. "
|
|
37
|
+
"As many files as can fit will go into each batch import job. This should be set in conjunction with the argument --runImportsOnWorkers."
|
|
38
38
|
)
|
|
39
|
+
|
|
40
|
+
# Deprecated
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--importWorkersThreshold", "--import-workers-threshold", dest="import_workers_batchsize",type=lambda x: human2bytes(str(x)), help=SUPPRESS
|
|
43
|
+
)
|
|
44
|
+
|
|
39
45
|
import_workers_disk_argument = ["--importWorkersDisk"]
|
|
40
46
|
if cwl:
|
|
41
47
|
import_workers_disk_argument.append("--import-workers-disk")
|
|
@@ -44,7 +50,8 @@ def add_runner_options(
|
|
|
44
50
|
dest="import_workers_disk",
|
|
45
51
|
type=lambda x: human2bytes(str(x)),
|
|
46
52
|
default="1 MiB",
|
|
47
|
-
help="Specify the disk size each import worker will get. This
|
|
48
|
-
"
|
|
49
|
-
"
|
|
53
|
+
help="Specify the disk size each import worker will get. This usually will not need to be set as Toil will attempt to use file streaming when downloading files. "
|
|
54
|
+
"If not possible, for example, when downloading from AWS to a GCE job store, "
|
|
55
|
+
"this should be set to the largest file size of all files to import. This should be set in conjunction with the arguments "
|
|
56
|
+
"--runImportsOnWorkers and --importWorkersBatchSize."
|
|
50
57
|
)
|
toil/options/wdl.py
CHANGED
|
@@ -86,3 +86,25 @@ def add_wdl_options(parser: ArgumentParser, suppress: bool = True) -> None:
|
|
|
86
86
|
default=None,
|
|
87
87
|
help=suppress_help or "Keep and return all call outputs as workflow outputs"
|
|
88
88
|
)
|
|
89
|
+
|
|
90
|
+
strict_arguments = ["--wdlStrict"] + (
|
|
91
|
+
["--strict"] if not suppress else []
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
*strict_arguments,
|
|
95
|
+
dest="strict",
|
|
96
|
+
type=strtobool,
|
|
97
|
+
default=False,
|
|
98
|
+
help=suppress_help or "Exit runner if workflow has any lint warnings"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
quant_check_arguments = ["--wdlQuantCheck"] + (
|
|
102
|
+
["--quantCheck"] if not suppress else []
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
*quant_check_arguments,
|
|
106
|
+
dest="quant_check",
|
|
107
|
+
type=strtobool,
|
|
108
|
+
default=True,
|
|
109
|
+
help=suppress_help or "Whether to relax quantifier validation rules"
|
|
110
|
+
)
|
|
@@ -109,6 +109,11 @@ _INSTANCE_PROFILE_ROLE_NAME = "toil"
|
|
|
109
109
|
_TAG_KEY_TOIL_NODE_TYPE = "ToilNodeType"
|
|
110
110
|
# The tag that specifies the cluster name on all nodes
|
|
111
111
|
_TAG_KEY_TOIL_CLUSTER_NAME = "clusterName"
|
|
112
|
+
# The tag we use to store the SSH key name.
|
|
113
|
+
# TODO: Get rid of this once
|
|
114
|
+
# <https://github.com/adamchainz/ec2-metadata/pull/562> is merged and we can
|
|
115
|
+
# get the SSH key name from the instance metadata.
|
|
116
|
+
_TAG_KEY_TOIL_SSH_KEY = "sshKeyName"
|
|
112
117
|
# How much storage on the root volume is expected to go to overhead and be
|
|
113
118
|
# unavailable to jobs when the node comes up?
|
|
114
119
|
# TODO: measure
|
|
@@ -309,11 +314,28 @@ class AWSProvisioner(AbstractProvisioner):
|
|
|
309
314
|
for tag in instance["Tags"]:
|
|
310
315
|
if tag.get("Key") == "Name":
|
|
311
316
|
self.clusterName = tag["Value"]
|
|
317
|
+
elif tag.get("Key") == _TAG_KEY_TOIL_SSH_KEY:
|
|
318
|
+
# If we can't get an SSH key from the instance metadata, we
|
|
319
|
+
# might be able to use this one from the tags.
|
|
320
|
+
self._keyName = tag["Value"]
|
|
312
321
|
# Determine what subnet we, the leader, are in
|
|
313
322
|
self._leader_subnet = instance["SubnetId"]
|
|
314
323
|
# Determine where to deploy workers.
|
|
315
324
|
self._worker_subnets_by_zone = self._get_good_subnets_like(self._leader_subnet)
|
|
316
325
|
|
|
326
|
+
# Find the SSH key name to use to start instances
|
|
327
|
+
if hasattr(ec2_metadata, 'public_keys') and isinstance(ec2_metadata.public_keys, dict):
|
|
328
|
+
key_names = list(ec2_metadata.public_keys.keys())
|
|
329
|
+
if len(key_names) > 0 and isinstance(key_names[0], str):
|
|
330
|
+
# We have a key name from the EC2 metadata. This should always
|
|
331
|
+
# be the case once
|
|
332
|
+
# <https://github.com/adamchainz/ec2-metadata/pull/562> is
|
|
333
|
+
# merged. Override anything from the tags.
|
|
334
|
+
self._keyName = key_names[0]
|
|
335
|
+
|
|
336
|
+
if not hasattr(self, '_keyName'):
|
|
337
|
+
raise RuntimeError("Unable to determine the SSH key name the cluster is using")
|
|
338
|
+
|
|
317
339
|
self._leaderPrivateIP = ec2_metadata.private_ipv4 # this is PRIVATE IP
|
|
318
340
|
self._tags = {
|
|
319
341
|
k: v
|
|
@@ -495,6 +517,7 @@ class AWSProvisioner(AbstractProvisioner):
|
|
|
495
517
|
# Make tags for the leader specifically
|
|
496
518
|
leader_tags = dict(self._tags)
|
|
497
519
|
leader_tags[_TAG_KEY_TOIL_NODE_TYPE] = "leader"
|
|
520
|
+
leader_tags[_TAG_KEY_TOIL_SSH_KEY] = self._keyName
|
|
498
521
|
logger.debug("Launching leader with tags: %s", leader_tags)
|
|
499
522
|
|
|
500
523
|
instances: list[Instance] = create_instances(
|
|
@@ -1144,7 +1167,7 @@ class AWSProvisioner(AbstractProvisioner):
|
|
|
1144
1167
|
workerInstances = [
|
|
1145
1168
|
i
|
|
1146
1169
|
for i in workerInstances
|
|
1147
|
-
if preemptible == (i
|
|
1170
|
+
if preemptible == (i.get("SpotInstanceRequestId") is not None)
|
|
1148
1171
|
]
|
|
1149
1172
|
logger.debug(
|
|
1150
1173
|
"%spreemptible workers found in cluster: %s",
|
|
@@ -1161,7 +1184,7 @@ class AWSProvisioner(AbstractProvisioner):
|
|
|
1161
1184
|
name=i["InstanceId"],
|
|
1162
1185
|
launchTime=i["LaunchTime"],
|
|
1163
1186
|
nodeType=i["InstanceType"],
|
|
1164
|
-
preemptible=i
|
|
1187
|
+
preemptible=i.get("SpotInstanceRequestId") is not None,
|
|
1165
1188
|
tags=collapse_tags(i["Tags"]),
|
|
1166
1189
|
)
|
|
1167
1190
|
for i in workerInstances
|