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.
Files changed (275) hide show
  1. toil/__init__.py +0 -35
  2. toil/batchSystems/abstractBatchSystem.py +1 -1
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
  4. toil/batchSystems/awsBatch.py +1 -1
  5. toil/batchSystems/cleanup_support.py +1 -1
  6. toil/batchSystems/kubernetes.py +53 -7
  7. toil/batchSystems/local_support.py +1 -1
  8. toil/batchSystems/mesos/batchSystem.py +13 -8
  9. toil/batchSystems/mesos/test/__init__.py +3 -2
  10. toil/batchSystems/registry.py +15 -118
  11. toil/batchSystems/singleMachine.py +1 -1
  12. toil/batchSystems/slurm.py +27 -26
  13. toil/bus.py +5 -3
  14. toil/common.py +59 -12
  15. toil/cwl/cwltoil.py +81 -38
  16. toil/cwl/utils.py +103 -3
  17. toil/job.py +64 -49
  18. toil/jobStores/abstractJobStore.py +35 -239
  19. toil/jobStores/aws/jobStore.py +2 -1
  20. toil/jobStores/fileJobStore.py +27 -2
  21. toil/jobStores/googleJobStore.py +110 -33
  22. toil/leader.py +9 -0
  23. toil/lib/accelerators.py +4 -2
  24. toil/lib/aws/utils.py.orig +504 -0
  25. toil/lib/bioio.py +1 -1
  26. toil/lib/docker.py +252 -91
  27. toil/lib/dockstore.py +11 -3
  28. toil/lib/exceptions.py +5 -3
  29. toil/lib/generatedEC2Lists.py +81 -19
  30. toil/lib/history.py +87 -13
  31. toil/lib/history_submission.py +23 -9
  32. toil/lib/io.py +34 -22
  33. toil/lib/misc.py +8 -2
  34. toil/lib/plugins.py +106 -0
  35. toil/lib/resources.py +2 -1
  36. toil/lib/threading.py +11 -10
  37. toil/lib/url.py +320 -0
  38. toil/options/common.py +8 -0
  39. toil/options/cwl.py +13 -1
  40. toil/options/runner.py +17 -10
  41. toil/options/wdl.py +22 -0
  42. toil/provisioners/aws/awsProvisioner.py +25 -2
  43. toil/server/api_spec/LICENSE +201 -0
  44. toil/server/api_spec/README.rst +5 -0
  45. toil/server/app.py +12 -6
  46. toil/server/cli/wes_cwl_runner.py +3 -2
  47. toil/server/wes/abstract_backend.py +21 -43
  48. toil/server/wes/toil_backend.py +2 -2
  49. toil/test/__init__.py +275 -115
  50. toil/test/batchSystems/batchSystemTest.py +228 -213
  51. toil/test/batchSystems/batch_system_plugin_test.py +7 -0
  52. toil/test/batchSystems/test_slurm.py +27 -0
  53. toil/test/cactus/pestis.tar.gz +0 -0
  54. toil/test/conftest.py +7 -0
  55. toil/test/cwl/2.fasta +11 -0
  56. toil/test/cwl/2.fastq +12 -0
  57. toil/test/cwl/conftest.py +1 -1
  58. toil/test/cwl/cwlTest.py +1175 -870
  59. toil/test/cwl/directory/directory/file.txt +15 -0
  60. toil/test/cwl/download_directory_file.json +4 -0
  61. toil/test/cwl/download_directory_s3.json +4 -0
  62. toil/test/cwl/download_file.json +6 -0
  63. toil/test/cwl/download_http.json +6 -0
  64. toil/test/cwl/download_https.json +6 -0
  65. toil/test/cwl/download_s3.json +6 -0
  66. toil/test/cwl/download_subdirectory_file.json +5 -0
  67. toil/test/cwl/download_subdirectory_s3.json +5 -0
  68. toil/test/cwl/empty.json +1 -0
  69. toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
  70. toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
  71. toil/test/cwl/optional-file-exists.json +6 -0
  72. toil/test/cwl/optional-file-missing.json +6 -0
  73. toil/test/cwl/preemptible_expression.json +1 -0
  74. toil/test/cwl/revsort-job-missing.json +6 -0
  75. toil/test/cwl/revsort-job.json +6 -0
  76. toil/test/cwl/s3_secondary_file.json +16 -0
  77. toil/test/cwl/seqtk_seq_job.json +6 -0
  78. toil/test/cwl/stream.json +6 -0
  79. toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
  80. toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
  81. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
  82. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
  83. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
  84. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
  85. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
  86. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
  87. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
  88. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
  89. toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
  90. toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
  91. toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
  92. toil/test/cwl/whale.txt +16 -0
  93. toil/test/docs/scripts/example_alwaysfail.py +38 -0
  94. toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
  95. toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
  96. toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
  97. toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
  98. toil/test/docs/scripts/tutorial_arguments.py +23 -0
  99. toil/test/docs/scripts/tutorial_debugging.patch +12 -0
  100. toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
  101. toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
  102. toil/test/docs/scripts/tutorial_docker.py +20 -0
  103. toil/test/docs/scripts/tutorial_dynamic.py +24 -0
  104. toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
  105. toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
  106. toil/test/docs/scripts/tutorial_helloworld.py +15 -0
  107. toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
  108. toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
  109. toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
  110. toil/test/docs/scripts/tutorial_managing.py +29 -0
  111. toil/test/docs/scripts/tutorial_managing2.py +56 -0
  112. toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
  113. toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
  114. toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
  115. toil/test/docs/scripts/tutorial_promises.py +25 -0
  116. toil/test/docs/scripts/tutorial_promises2.py +30 -0
  117. toil/test/docs/scripts/tutorial_quickstart.py +22 -0
  118. toil/test/docs/scripts/tutorial_requirements.py +44 -0
  119. toil/test/docs/scripts/tutorial_services.py +45 -0
  120. toil/test/docs/scripts/tutorial_staging.py +45 -0
  121. toil/test/docs/scripts/tutorial_stats.py +64 -0
  122. toil/test/docs/scriptsTest.py +2 -1
  123. toil/test/lib/aws/test_iam.py +3 -1
  124. toil/test/lib/dockerTest.py +205 -122
  125. toil/test/lib/test_history.py +101 -77
  126. toil/test/lib/test_url.py +69 -0
  127. toil/test/lib/url_plugin_test.py +105 -0
  128. toil/test/provisioners/aws/awsProvisionerTest.py +13 -10
  129. toil/test/provisioners/clusterTest.py +17 -4
  130. toil/test/provisioners/gceProvisionerTest.py +17 -15
  131. toil/test/server/serverTest.py +78 -36
  132. toil/test/sort/sort.py +4 -1
  133. toil/test/src/busTest.py +17 -17
  134. toil/test/src/deferredFunctionTest.py +145 -132
  135. toil/test/src/importExportFileTest.py +71 -63
  136. toil/test/src/jobEncapsulationTest.py +27 -28
  137. toil/test/src/jobServiceTest.py +149 -133
  138. toil/test/src/jobTest.py +219 -211
  139. toil/test/src/miscTests.py +66 -60
  140. toil/test/src/promisedRequirementTest.py +163 -169
  141. toil/test/src/regularLogTest.py +24 -24
  142. toil/test/src/resourceTest.py +82 -76
  143. toil/test/src/restartDAGTest.py +51 -47
  144. toil/test/src/resumabilityTest.py +24 -19
  145. toil/test/src/retainTempDirTest.py +60 -57
  146. toil/test/src/systemTest.py +17 -13
  147. toil/test/src/threadingTest.py +29 -32
  148. toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
  149. toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
  150. toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
  151. toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
  152. toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
  153. toil/test/utils/toilDebugTest.py +117 -102
  154. toil/test/utils/toilKillTest.py +54 -53
  155. toil/test/utils/utilsTest.py +303 -229
  156. toil/test/wdl/lint_error.wdl +9 -0
  157. toil/test/wdl/md5sum/empty_file.json +1 -0
  158. toil/test/wdl/md5sum/md5sum-gs.json +1 -0
  159. toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
  160. toil/test/wdl/md5sum/md5sum.input +1 -0
  161. toil/test/wdl/md5sum/md5sum.json +1 -0
  162. toil/test/wdl/md5sum/md5sum.wdl +25 -0
  163. toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
  164. toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
  165. toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
  166. toil/test/wdl/standard_library/as_map.json +16 -0
  167. toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
  168. toil/test/wdl/standard_library/as_pairs.json +7 -0
  169. toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
  170. toil/test/wdl/standard_library/ceil.json +3 -0
  171. toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
  172. toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
  173. toil/test/wdl/standard_library/collect_by_key.json +1 -0
  174. toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
  175. toil/test/wdl/standard_library/cross.json +11 -0
  176. toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
  177. toil/test/wdl/standard_library/flatten.json +7 -0
  178. toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
  179. toil/test/wdl/standard_library/floor.json +3 -0
  180. toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
  181. toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
  182. toil/test/wdl/standard_library/keys.json +8 -0
  183. toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
  184. toil/test/wdl/standard_library/length.json +7 -0
  185. toil/test/wdl/standard_library/length_as_input.wdl +16 -0
  186. toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
  187. toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
  188. toil/test/wdl/standard_library/length_invalid.json +3 -0
  189. toil/test/wdl/standard_library/range.json +3 -0
  190. toil/test/wdl/standard_library/range_0.json +3 -0
  191. toil/test/wdl/standard_library/range_as_input.wdl +17 -0
  192. toil/test/wdl/standard_library/range_invalid.json +3 -0
  193. toil/test/wdl/standard_library/read_boolean.json +3 -0
  194. toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
  195. toil/test/wdl/standard_library/read_float.json +3 -0
  196. toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
  197. toil/test/wdl/standard_library/read_int.json +3 -0
  198. toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
  199. toil/test/wdl/standard_library/read_json.json +3 -0
  200. toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
  201. toil/test/wdl/standard_library/read_lines.json +3 -0
  202. toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
  203. toil/test/wdl/standard_library/read_map.json +3 -0
  204. toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
  205. toil/test/wdl/standard_library/read_string.json +3 -0
  206. toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
  207. toil/test/wdl/standard_library/read_tsv.json +3 -0
  208. toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
  209. toil/test/wdl/standard_library/round.json +3 -0
  210. toil/test/wdl/standard_library/round_as_command.wdl +16 -0
  211. toil/test/wdl/standard_library/round_as_input.wdl +16 -0
  212. toil/test/wdl/standard_library/size.json +3 -0
  213. toil/test/wdl/standard_library/size_as_command.wdl +17 -0
  214. toil/test/wdl/standard_library/size_as_output.wdl +36 -0
  215. toil/test/wdl/standard_library/stderr.json +3 -0
  216. toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
  217. toil/test/wdl/standard_library/stdout.json +3 -0
  218. toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
  219. toil/test/wdl/standard_library/sub.json +3 -0
  220. toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
  221. toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
  222. toil/test/wdl/standard_library/transpose.json +6 -0
  223. toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
  224. toil/test/wdl/standard_library/write_json.json +6 -0
  225. toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
  226. toil/test/wdl/standard_library/write_lines.json +7 -0
  227. toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
  228. toil/test/wdl/standard_library/write_map.json +6 -0
  229. toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
  230. toil/test/wdl/standard_library/write_tsv.json +6 -0
  231. toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
  232. toil/test/wdl/standard_library/zip.json +12 -0
  233. toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
  234. toil/test/wdl/test.csv +3 -0
  235. toil/test/wdl/test.tsv +3 -0
  236. toil/test/wdl/testfiles/croo.wdl +38 -0
  237. toil/test/wdl/testfiles/drop_files.wdl +62 -0
  238. toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
  239. toil/test/wdl/testfiles/empty.txt +0 -0
  240. toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
  241. toil/test/wdl/testfiles/random.wdl +66 -0
  242. toil/test/wdl/testfiles/read_file.wdl +18 -0
  243. toil/test/wdl/testfiles/string_file_coercion.json +1 -0
  244. toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
  245. toil/test/wdl/testfiles/test.json +4 -0
  246. toil/test/wdl/testfiles/test_boolean.txt +1 -0
  247. toil/test/wdl/testfiles/test_float.txt +1 -0
  248. toil/test/wdl/testfiles/test_int.txt +1 -0
  249. toil/test/wdl/testfiles/test_lines.txt +5 -0
  250. toil/test/wdl/testfiles/test_map.txt +2 -0
  251. toil/test/wdl/testfiles/test_string.txt +1 -0
  252. toil/test/wdl/testfiles/url_to_file.wdl +13 -0
  253. toil/test/wdl/testfiles/url_to_optional_file.wdl +14 -0
  254. toil/test/wdl/testfiles/vocab.json +1 -0
  255. toil/test/wdl/testfiles/vocab.wdl +66 -0
  256. toil/test/wdl/testfiles/wait.wdl +34 -0
  257. toil/test/wdl/wdl_specification/type_pair.json +23 -0
  258. toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
  259. toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
  260. toil/test/wdl/wdl_specification/v1_spec.json +1 -0
  261. toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
  262. toil/test/wdl/wdltoil_test.py +751 -529
  263. toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
  264. toil/utils/toilSshCluster.py +23 -0
  265. toil/utils/toilUpdateEC2Instances.py +1 -0
  266. toil/version.py +5 -5
  267. toil/wdl/wdltoil.py +518 -437
  268. toil/worker.py +11 -6
  269. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/METADATA +25 -24
  270. toil-9.0.0.dist-info/RECORD +444 -0
  271. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/WHEEL +1 -1
  272. toil-8.1.0b1.dist-info/RECORD +0 -259
  273. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/entry_points.txt +0 -0
  274. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info/licenses}/LICENSE +0 -0
  275. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/top_level.txt +0 -0
@@ -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
- class GoogleJobStore(AbstractJobStore):
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
- def create_client(cls) -> storage.Client:
165
+ @memoize
166
+ def create_client(cls) -> tuple[storage.Client, str]:
124
167
  """
125
- Produce a client for Google Sotrage with the highest level of access we can get.
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
- log.warning(
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 override path is not set
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
- log.warning(
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 urlparse.ParseResult url: the URL
482
+ :param client: The Google Sotrage client to use to connect with.
416
483
 
417
- :param bool exists: if True, then syncs local blob object with cloud
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
- storageClient = cls.create_client()
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
- try:
444
- cls._get_blob_from_url(url, exists=True)
445
- return True
446
- except NoSuchFileException:
447
- return False
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
- return cls._get_blob_from_url(url, exists=True).size
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
- blob = cls._get_blob_from_url(url, exists=True)
456
- blob.download_to_file(writable)
457
- return blob.size, False
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
- blob = cls._get_blob_from_url(url, exists=True)
462
- return blob.open("rb")
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
- blob = cls._get_blob_from_url(url)
471
- blob.upload_from_file(readable)
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,