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/jobStores/googleJobStore.py
CHANGED
|
@@ -20,17 +20,18 @@ import uuid
|
|
|
20
20
|
from contextlib import contextmanager
|
|
21
21
|
from functools import wraps
|
|
22
22
|
from io import BytesIO
|
|
23
|
-
from typing import IO, Optional
|
|
24
|
-
from urllib.parse import ParseResult
|
|
23
|
+
from typing import Any, IO, Iterator, Optional
|
|
24
|
+
from urllib.parse import ParseResult, urlunparse
|
|
25
25
|
|
|
26
26
|
from google.api_core.exceptions import (
|
|
27
27
|
GoogleAPICallError,
|
|
28
28
|
InternalServerError,
|
|
29
29
|
ServiceUnavailable,
|
|
30
30
|
)
|
|
31
|
-
from google.auth.exceptions import DefaultCredentialsError
|
|
31
|
+
from google.auth.exceptions import DefaultCredentialsError, InvalidOperation
|
|
32
32
|
from google.cloud import exceptions, storage
|
|
33
33
|
|
|
34
|
+
from toil import memoize
|
|
34
35
|
from toil.jobStores.abstractJobStore import (
|
|
35
36
|
AbstractJobStore,
|
|
36
37
|
JobStoreExistsException,
|
|
@@ -43,6 +44,7 @@ from toil.lib.compatibility import compat_bytes
|
|
|
43
44
|
from toil.lib.io import AtomicFileCreate
|
|
44
45
|
from toil.lib.misc import truncExpBackoff
|
|
45
46
|
from toil.lib.retry import old_retry
|
|
47
|
+
from toil.lib.url import URLAccess
|
|
46
48
|
|
|
47
49
|
log = logging.getLogger(__name__)
|
|
48
50
|
|
|
@@ -90,8 +92,48 @@ def google_retry(f):
|
|
|
90
92
|
|
|
91
93
|
return wrapper
|
|
92
94
|
|
|
95
|
+
@contextmanager
|
|
96
|
+
def permission_error_reporter(url: ParseResult, notes: str) -> Iterator[None]:
|
|
97
|
+
"""
|
|
98
|
+
Detect and usefully report permission errors.
|
|
99
|
+
|
|
100
|
+
If we fall back to anonymous credentials, but they don't have permission
|
|
101
|
+
for something, the Google Cloud Storage module will try to refresh them
|
|
102
|
+
behind the scenes. Then it will complain::
|
|
103
|
+
|
|
104
|
+
<class 'google.auth.exceptions.InvalidOperation'>: Anonymous credentials cannot be refreshed.
|
|
105
|
+
|
|
106
|
+
We need to detect this and report that the real problem is that the user
|
|
107
|
+
has not set up any credentials. When you try to make the client
|
|
108
|
+
non-anonymously and don't have credentials set up, you get a nice error
|
|
109
|
+
from Google::
|
|
110
|
+
|
|
111
|
+
google.auth.exceptions.DefaultCredentialsError: Your default credentials were not found. To set up Application Default Credentials, see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.
|
|
112
|
+
|
|
113
|
+
But we swallow that when we fall back to anonymous access.
|
|
114
|
+
|
|
115
|
+
So we take the URL and any notes from client setup here, and if something
|
|
116
|
+
goes wrong that looks like a permission problem we complain with the notes
|
|
117
|
+
attached.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
yield
|
|
121
|
+
except InvalidOperation as e:
|
|
122
|
+
if "Anonymous credentials cannot be refreshed" in str(e):
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
"Google Storage tried to refresh anonymous credentials. "
|
|
125
|
+
"Are you sure you have set up your Google Account login "
|
|
126
|
+
"for applications with permission to access "
|
|
127
|
+
f"{urlunparse(url)}? "
|
|
128
|
+
"Maybe try `gcloud auth application-default login`? "
|
|
129
|
+
f"Client setup said: {notes}"
|
|
130
|
+
) from e
|
|
131
|
+
else:
|
|
132
|
+
raise
|
|
133
|
+
|
|
93
134
|
|
|
94
|
-
|
|
135
|
+
|
|
136
|
+
class GoogleJobStore(AbstractJobStore, URLAccess):
|
|
95
137
|
|
|
96
138
|
nodeServiceAccountJson = "/root/service_account.json"
|
|
97
139
|
|
|
@@ -117,19 +159,40 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
117
159
|
self.readStatsBaseID = self.statsReadPrefix + self.statsBaseID
|
|
118
160
|
|
|
119
161
|
self.sseKey = None
|
|
120
|
-
self.storageClient = self.create_client()
|
|
162
|
+
self.storageClient, self.auth_notes = self.create_client()
|
|
121
163
|
|
|
122
164
|
@classmethod
|
|
123
|
-
|
|
165
|
+
@memoize
|
|
166
|
+
def create_client(cls) -> tuple[storage.Client, str]:
|
|
124
167
|
"""
|
|
125
|
-
Produce a client for Google
|
|
168
|
+
Produce a client for Google Storage with the highest level of access we can get.
|
|
126
169
|
|
|
127
170
|
Fall back to anonymous access if no project is available, unlike the
|
|
128
171
|
Google Storage module's behavior.
|
|
129
172
|
|
|
130
173
|
Warn if GOOGLE_APPLICATION_CREDENTIALS is set but not actually present.
|
|
174
|
+
|
|
175
|
+
:returns: the client, and any notes about why it might not have permissions.
|
|
131
176
|
"""
|
|
132
177
|
|
|
178
|
+
notes: list[str] = []
|
|
179
|
+
def add_note(message: str, *args: Any, warn: bool = False) -> None:
|
|
180
|
+
"""
|
|
181
|
+
Add and possibly warn with a note about the client permissions.
|
|
182
|
+
"""
|
|
183
|
+
note = message % args
|
|
184
|
+
if warn:
|
|
185
|
+
log.warning(note)
|
|
186
|
+
notes.append(note)
|
|
187
|
+
def compile_notes() -> str:
|
|
188
|
+
"""
|
|
189
|
+
Make one string explainign why we might not have expected permissions.
|
|
190
|
+
"""
|
|
191
|
+
if notes:
|
|
192
|
+
return f"Google authentication had {len(notes)} potential issues: {'; '.join(notes)}"
|
|
193
|
+
else:
|
|
194
|
+
return "Google authentication appeared successful."
|
|
195
|
+
|
|
133
196
|
# Determine if we have an override environment variable for our credentials.
|
|
134
197
|
# We get the path to check existence, but Google Storage works out what
|
|
135
198
|
# to use later by looking at the environment again.
|
|
@@ -139,38 +202,42 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
139
202
|
if credentials_path is not None and not os.path.exists(credentials_path):
|
|
140
203
|
# If the file is missing, complain.
|
|
141
204
|
# This variable holds a file name and not any sensitive data itself.
|
|
142
|
-
|
|
205
|
+
add_note(
|
|
143
206
|
"File '%s' from GOOGLE_APPLICATION_CREDENTIALS is unavailable! "
|
|
144
207
|
"We may not be able to authenticate!",
|
|
145
208
|
credentials_path,
|
|
209
|
+
warn=True
|
|
146
210
|
)
|
|
147
211
|
|
|
148
212
|
if credentials_path is None and os.path.exists(cls.nodeServiceAccountJson):
|
|
149
213
|
try:
|
|
150
|
-
# load credentials from a particular file on GCE nodes if an
|
|
214
|
+
# load credentials from a particular file on GCE nodes if an
|
|
215
|
+
# override path is not set
|
|
151
216
|
return storage.Client.from_service_account_json(
|
|
152
217
|
cls.nodeServiceAccountJson
|
|
153
|
-
)
|
|
218
|
+
), compile_notes()
|
|
154
219
|
except OSError:
|
|
155
220
|
# Probably we don't have permission to use the file.
|
|
156
|
-
|
|
221
|
+
add_note(
|
|
157
222
|
"File '%s' exists but didn't work to authenticate!",
|
|
158
223
|
cls.nodeServiceAccountJson,
|
|
224
|
+
warn=True
|
|
159
225
|
)
|
|
160
226
|
|
|
161
227
|
# Either a filename is specified, or our fallback file isn't there.
|
|
162
228
|
try:
|
|
163
229
|
# See if Google can work out how to authenticate.
|
|
164
|
-
return storage.Client()
|
|
165
|
-
except (DefaultCredentialsError, OSError):
|
|
230
|
+
return storage.Client(), compile_notes()
|
|
231
|
+
except (DefaultCredentialsError, OSError) as e:
|
|
166
232
|
# Depending on which Google codepath or module version (???)
|
|
167
233
|
# realizes we have no credentials, we can get an EnvironemntError,
|
|
168
234
|
# or the new DefaultCredentialsError we are supposedly specced to
|
|
169
235
|
# get.
|
|
236
|
+
add_note("Could not make authenticated client: %s", e)
|
|
170
237
|
|
|
171
238
|
# Google can't find credentials, fall back to being anonymous.
|
|
172
239
|
# This is likely to happen all the time so don't warn.
|
|
173
|
-
return storage.Client.create_anonymous_client()
|
|
240
|
+
return storage.Client.create_anonymous_client(), compile_notes()
|
|
174
241
|
|
|
175
242
|
@google_retry
|
|
176
243
|
def initialize(self, config=None):
|
|
@@ -406,19 +473,20 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
406
473
|
|
|
407
474
|
@classmethod
|
|
408
475
|
@google_retry
|
|
409
|
-
def _get_blob_from_url(cls, url, exists=False):
|
|
476
|
+
def _get_blob_from_url(cls, client: storage.Client, url: ParseResult, exists: bool = False) -> storage.blob.Blob:
|
|
410
477
|
"""
|
|
411
478
|
Gets the blob specified by the url.
|
|
412
479
|
|
|
413
480
|
caution: makes no api request. blob may not ACTUALLY exist
|
|
414
481
|
|
|
415
|
-
:param
|
|
482
|
+
:param client: The Google Sotrage client to use to connect with.
|
|
416
483
|
|
|
417
|
-
:param
|
|
484
|
+
:param url: the URL
|
|
485
|
+
|
|
486
|
+
:param exists: if True, then syncs local blob object with cloud
|
|
418
487
|
and raises exceptions if it doesn't exist remotely
|
|
419
488
|
|
|
420
489
|
:return: the blob requested
|
|
421
|
-
:rtype: :class:`~google.cloud.storage.blob.Blob`
|
|
422
490
|
"""
|
|
423
491
|
bucketName = url.netloc
|
|
424
492
|
fileName = url.path
|
|
@@ -427,8 +495,7 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
427
495
|
if fileName.startswith("/"):
|
|
428
496
|
fileName = fileName[1:]
|
|
429
497
|
|
|
430
|
-
|
|
431
|
-
bucket = storageClient.bucket(bucket_name=bucketName)
|
|
498
|
+
bucket = client.bucket(bucket_name=bucketName)
|
|
432
499
|
blob = bucket.blob(compat_bytes(fileName))
|
|
433
500
|
|
|
434
501
|
if exists:
|
|
@@ -440,26 +507,34 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
440
507
|
|
|
441
508
|
@classmethod
|
|
442
509
|
def _url_exists(cls, url: ParseResult) -> bool:
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
510
|
+
client, auth_notes = cls.create_client()
|
|
511
|
+
with permission_error_reporter(url, auth_notes):
|
|
512
|
+
try:
|
|
513
|
+
cls._get_blob_from_url(client, url, exists=True)
|
|
514
|
+
return True
|
|
515
|
+
except NoSuchFileException:
|
|
516
|
+
return False
|
|
448
517
|
|
|
449
518
|
@classmethod
|
|
450
519
|
def _get_size(cls, url):
|
|
451
|
-
|
|
520
|
+
client, auth_notes = cls.create_client()
|
|
521
|
+
with permission_error_reporter(url, auth_notes):
|
|
522
|
+
return cls._get_blob_from_url(client, url, exists=True).size
|
|
452
523
|
|
|
453
524
|
@classmethod
|
|
454
525
|
def _read_from_url(cls, url, writable):
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
526
|
+
client, auth_notes = cls.create_client()
|
|
527
|
+
with permission_error_reporter(url, auth_notes):
|
|
528
|
+
blob = cls._get_blob_from_url(client, url, exists=True)
|
|
529
|
+
blob.download_to_file(writable)
|
|
530
|
+
return blob.size, False
|
|
458
531
|
|
|
459
532
|
@classmethod
|
|
460
533
|
def _open_url(cls, url: ParseResult) -> IO[bytes]:
|
|
461
|
-
|
|
462
|
-
|
|
534
|
+
client, auth_notes = cls.create_client()
|
|
535
|
+
with permission_error_reporter(url, auth_notes):
|
|
536
|
+
blob = cls._get_blob_from_url(client, url, exists=True)
|
|
537
|
+
return blob.open("rb")
|
|
463
538
|
|
|
464
539
|
@classmethod
|
|
465
540
|
def _supports_url(cls, url, export=False):
|
|
@@ -467,8 +542,10 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
467
542
|
|
|
468
543
|
@classmethod
|
|
469
544
|
def _write_to_url(cls, readable: bytes, url: str, executable: bool = False) -> None:
|
|
470
|
-
|
|
471
|
-
|
|
545
|
+
client, auth_notes = cls.create_client()
|
|
546
|
+
with permission_error_reporter(url, auth_notes):
|
|
547
|
+
blob = cls._get_blob_from_url(client, url)
|
|
548
|
+
blob.upload_from_file(readable)
|
|
472
549
|
|
|
473
550
|
@classmethod
|
|
474
551
|
def _list_url(cls, url: ParseResult) -> list[str]:
|
toil/leader.py
CHANGED
|
@@ -1780,6 +1780,15 @@ class Leader:
|
|
|
1780
1780
|
|
|
1781
1781
|
self._updatePredecessorStatus(job_id)
|
|
1782
1782
|
|
|
1783
|
+
if self.config.stop_on_first_failure:
|
|
1784
|
+
# We want to stop the workflow on the first complete failure of a job.
|
|
1785
|
+
logger.error("Stopping workflow on first failure, which was: %s", job_desc)
|
|
1786
|
+
raise FailedJobsException(
|
|
1787
|
+
self.jobStore,
|
|
1788
|
+
[self.toilState.get_job(job_id)],
|
|
1789
|
+
exit_code=self.recommended_fail_exit_code,
|
|
1790
|
+
)
|
|
1791
|
+
|
|
1783
1792
|
def _updatePredecessorStatus(self, jobStoreID: str) -> None:
|
|
1784
1793
|
"""Update status of predecessors for finished (possibly failed) successor job."""
|
|
1785
1794
|
if jobStoreID in self.toilState.service_to_client:
|
toil/lib/accelerators.py
CHANGED
|
@@ -34,7 +34,7 @@ def have_working_nvidia_smi() -> bool:
|
|
|
34
34
|
it can fulfill a CUDARequirement.
|
|
35
35
|
"""
|
|
36
36
|
try:
|
|
37
|
-
subprocess.check_call(["nvidia-smi"])
|
|
37
|
+
subprocess.check_call(["nvidia-smi"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
38
38
|
except (
|
|
39
39
|
FileNotFoundError,
|
|
40
40
|
PermissionError,
|
|
@@ -103,7 +103,9 @@ def have_working_nvidia_docker_runtime() -> bool:
|
|
|
103
103
|
"all",
|
|
104
104
|
"ubuntu:20.04",
|
|
105
105
|
"nvidia-smi",
|
|
106
|
-
]
|
|
106
|
+
],
|
|
107
|
+
stdout=subprocess.DEVNULL,
|
|
108
|
+
stderr=subprocess.DEVNULL
|
|
107
109
|
)
|
|
108
110
|
except (
|
|
109
111
|
FileNotFoundError,
|