toil 7.0.0__py3-none-any.whl → 8.1.0b1__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 (197) hide show
  1. toil/__init__.py +124 -86
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +137 -77
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +211 -101
  5. toil/batchSystems/awsBatch.py +237 -128
  6. toil/batchSystems/cleanup_support.py +22 -16
  7. toil/batchSystems/contained_executor.py +30 -26
  8. toil/batchSystems/gridengine.py +85 -49
  9. toil/batchSystems/htcondor.py +164 -87
  10. toil/batchSystems/kubernetes.py +622 -386
  11. toil/batchSystems/local_support.py +17 -12
  12. toil/batchSystems/lsf.py +132 -79
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +288 -149
  16. toil/batchSystems/mesos/executor.py +77 -49
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +39 -29
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +293 -123
  21. toil/batchSystems/slurm.py +651 -155
  22. toil/batchSystems/torque.py +46 -32
  23. toil/bus.py +141 -73
  24. toil/common.py +784 -397
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1137 -534
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +62 -41
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +88 -57
  32. toil/fileStores/cachingFileStore.py +711 -247
  33. toil/fileStores/nonCachingFileStore.py +113 -75
  34. toil/job.py +1031 -349
  35. toil/jobStores/abstractJobStore.py +387 -243
  36. toil/jobStores/aws/jobStore.py +772 -412
  37. toil/jobStores/aws/utils.py +161 -109
  38. toil/jobStores/conftest.py +1 -0
  39. toil/jobStores/fileJobStore.py +289 -151
  40. toil/jobStores/googleJobStore.py +137 -70
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +614 -269
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +55 -28
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +204 -58
  49. toil/lib/aws/utils.py +290 -213
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +83 -49
  53. toil/lib/docker.py +131 -103
  54. toil/lib/dockstore.py +379 -0
  55. toil/lib/ec2.py +322 -209
  56. toil/lib/ec2nodes.py +174 -105
  57. toil/lib/encryption/_dummy.py +5 -3
  58. toil/lib/encryption/_nacl.py +10 -6
  59. toil/lib/encryption/conftest.py +1 -0
  60. toil/lib/exceptions.py +26 -7
  61. toil/lib/expando.py +4 -2
  62. toil/lib/ftp_utils.py +217 -0
  63. toil/lib/generatedEC2Lists.py +127 -19
  64. toil/lib/history.py +1271 -0
  65. toil/lib/history_submission.py +681 -0
  66. toil/lib/humanize.py +6 -2
  67. toil/lib/io.py +121 -12
  68. toil/lib/iterables.py +4 -2
  69. toil/lib/memoize.py +12 -8
  70. toil/lib/misc.py +83 -18
  71. toil/lib/objects.py +2 -2
  72. toil/lib/resources.py +19 -7
  73. toil/lib/retry.py +125 -87
  74. toil/lib/threading.py +282 -80
  75. toil/lib/throttle.py +15 -14
  76. toil/lib/trs.py +390 -0
  77. toil/lib/web.py +38 -0
  78. toil/options/common.py +850 -402
  79. toil/options/cwl.py +185 -90
  80. toil/options/runner.py +50 -0
  81. toil/options/wdl.py +70 -19
  82. toil/provisioners/__init__.py +111 -46
  83. toil/provisioners/abstractProvisioner.py +322 -157
  84. toil/provisioners/aws/__init__.py +62 -30
  85. toil/provisioners/aws/awsProvisioner.py +980 -627
  86. toil/provisioners/clusterScaler.py +541 -279
  87. toil/provisioners/gceProvisioner.py +283 -180
  88. toil/provisioners/node.py +147 -79
  89. toil/realtimeLogger.py +34 -22
  90. toil/resource.py +137 -75
  91. toil/server/app.py +127 -61
  92. toil/server/celery_app.py +3 -1
  93. toil/server/cli/wes_cwl_runner.py +84 -55
  94. toil/server/utils.py +56 -31
  95. toil/server/wes/abstract_backend.py +64 -26
  96. toil/server/wes/amazon_wes_utils.py +21 -15
  97. toil/server/wes/tasks.py +121 -63
  98. toil/server/wes/toil_backend.py +142 -107
  99. toil/server/wsgi_app.py +4 -3
  100. toil/serviceManager.py +58 -22
  101. toil/statsAndLogging.py +183 -65
  102. toil/test/__init__.py +263 -179
  103. toil/test/batchSystems/batchSystemTest.py +438 -195
  104. toil/test/batchSystems/batch_system_plugin_test.py +18 -7
  105. toil/test/batchSystems/test_gridengine.py +173 -0
  106. toil/test/batchSystems/test_lsf_helper.py +67 -58
  107. toil/test/batchSystems/test_slurm.py +265 -49
  108. toil/test/cactus/test_cactus_integration.py +20 -22
  109. toil/test/cwl/conftest.py +39 -0
  110. toil/test/cwl/cwlTest.py +375 -72
  111. toil/test/cwl/measure_default_memory.cwl +12 -0
  112. toil/test/cwl/not_run_required_input.cwl +29 -0
  113. toil/test/cwl/optional-file.cwl +18 -0
  114. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  115. toil/test/docs/scriptsTest.py +60 -34
  116. toil/test/jobStores/jobStoreTest.py +412 -235
  117. toil/test/lib/aws/test_iam.py +116 -48
  118. toil/test/lib/aws/test_s3.py +16 -9
  119. toil/test/lib/aws/test_utils.py +5 -6
  120. toil/test/lib/dockerTest.py +118 -141
  121. toil/test/lib/test_conversions.py +113 -115
  122. toil/test/lib/test_ec2.py +57 -49
  123. toil/test/lib/test_history.py +212 -0
  124. toil/test/lib/test_misc.py +12 -5
  125. toil/test/lib/test_trs.py +161 -0
  126. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  127. toil/test/mesos/helloWorld.py +7 -6
  128. toil/test/mesos/stress.py +25 -20
  129. toil/test/options/options.py +7 -2
  130. toil/test/provisioners/aws/awsProvisionerTest.py +293 -140
  131. toil/test/provisioners/clusterScalerTest.py +440 -250
  132. toil/test/provisioners/clusterTest.py +81 -42
  133. toil/test/provisioners/gceProvisionerTest.py +174 -100
  134. toil/test/provisioners/provisionerTest.py +25 -13
  135. toil/test/provisioners/restartScript.py +5 -4
  136. toil/test/server/serverTest.py +188 -141
  137. toil/test/sort/restart_sort.py +137 -68
  138. toil/test/sort/sort.py +134 -66
  139. toil/test/sort/sortTest.py +91 -49
  140. toil/test/src/autoDeploymentTest.py +140 -100
  141. toil/test/src/busTest.py +20 -18
  142. toil/test/src/checkpointTest.py +8 -2
  143. toil/test/src/deferredFunctionTest.py +49 -35
  144. toil/test/src/dockerCheckTest.py +33 -26
  145. toil/test/src/environmentTest.py +20 -10
  146. toil/test/src/fileStoreTest.py +538 -271
  147. toil/test/src/helloWorldTest.py +7 -4
  148. toil/test/src/importExportFileTest.py +61 -31
  149. toil/test/src/jobDescriptionTest.py +32 -17
  150. toil/test/src/jobEncapsulationTest.py +2 -0
  151. toil/test/src/jobFileStoreTest.py +74 -50
  152. toil/test/src/jobServiceTest.py +187 -73
  153. toil/test/src/jobTest.py +120 -70
  154. toil/test/src/miscTests.py +19 -18
  155. toil/test/src/promisedRequirementTest.py +82 -36
  156. toil/test/src/promisesTest.py +7 -6
  157. toil/test/src/realtimeLoggerTest.py +6 -6
  158. toil/test/src/regularLogTest.py +71 -37
  159. toil/test/src/resourceTest.py +80 -49
  160. toil/test/src/restartDAGTest.py +36 -22
  161. toil/test/src/resumabilityTest.py +9 -2
  162. toil/test/src/retainTempDirTest.py +45 -14
  163. toil/test/src/systemTest.py +12 -8
  164. toil/test/src/threadingTest.py +44 -25
  165. toil/test/src/toilContextManagerTest.py +10 -7
  166. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  167. toil/test/src/workerTest.py +33 -16
  168. toil/test/utils/toilDebugTest.py +70 -58
  169. toil/test/utils/toilKillTest.py +4 -5
  170. toil/test/utils/utilsTest.py +239 -102
  171. toil/test/wdl/wdltoil_test.py +789 -148
  172. toil/test/wdl/wdltoil_test_kubernetes.py +37 -23
  173. toil/toilState.py +52 -26
  174. toil/utils/toilConfig.py +13 -4
  175. toil/utils/toilDebugFile.py +44 -27
  176. toil/utils/toilDebugJob.py +85 -25
  177. toil/utils/toilDestroyCluster.py +11 -6
  178. toil/utils/toilKill.py +8 -3
  179. toil/utils/toilLaunchCluster.py +251 -145
  180. toil/utils/toilMain.py +37 -16
  181. toil/utils/toilRsyncCluster.py +27 -14
  182. toil/utils/toilSshCluster.py +45 -22
  183. toil/utils/toilStats.py +75 -36
  184. toil/utils/toilStatus.py +226 -119
  185. toil/utils/toilUpdateEC2Instances.py +3 -1
  186. toil/version.py +6 -6
  187. toil/wdl/utils.py +5 -5
  188. toil/wdl/wdltoil.py +3528 -1053
  189. toil/worker.py +370 -149
  190. toil-8.1.0b1.dist-info/METADATA +178 -0
  191. toil-8.1.0b1.dist-info/RECORD +259 -0
  192. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/WHEEL +1 -1
  193. toil-7.0.0.dist-info/METADATA +0 -158
  194. toil-7.0.0.dist-info/RECORD +0 -244
  195. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/LICENSE +0 -0
  196. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/entry_points.txt +0 -0
  197. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/top_level.txt +0 -0
@@ -13,9 +13,7 @@
13
13
  # limitations under the License.
14
14
  import logging
15
15
 
16
- from toil.lib.conversions import (convert_units,
17
- hms_duration_to_seconds,
18
- human2bytes)
16
+ from toil.lib.conversions import convert_units, hms_duration_to_seconds, human2bytes
19
17
  from toil.test import ToilTest
20
18
 
21
19
  logger = logging.getLogger(__name__)
@@ -79,135 +77,135 @@ class ConversionTest(ToilTest):
79
77
  "11234234 KB": "0.0112 TB",
80
78
  "11234234 MB": "11.2342 TB",
81
79
  "11234234 GB": "11234.2340 TB",
82
- "11234234 TB": "11234234.0000 TB"
80
+ "11234234 TB": "11234234.0000 TB",
83
81
  }
84
82
  results = {}
85
83
  for i in (0, 0.1, 0.5, 0.9, 1, 7, 7.42423, 10, 100, 1000, 11234234):
86
- for src_unit in ['B', 'KB', 'MB', 'GB', 'TB']:
87
- for dst_unit in ['B', 'KB', 'MB', 'GB', 'TB']:
84
+ for src_unit in ["B", "KB", "MB", "GB", "TB"]:
85
+ for dst_unit in ["B", "KB", "MB", "GB", "TB"]:
88
86
  converted = convert_units(i, src_unit, dst_unit)
89
- results[f'{i} {src_unit}'] = f'{converted:.4f} {dst_unit}'
87
+ results[f"{i} {src_unit}"] = f"{converted:.4f} {dst_unit}"
90
88
  self.assertEqual(results, expected_conversions)
91
89
 
92
90
  def test_human2bytes(self):
93
91
  expected_results = {
94
- '0 b': 0,
95
- '0 Ki': 0,
96
- '0 Mi': 0,
97
- '0 Gi': 0,
98
- '0 Ti': 0,
99
- '0 K': 0,
100
- '0 M': 0,
101
- '0 G': 0,
102
- '0 T': 0,
103
- '0.1 b': 0,
104
- '0.1 Ki': 102,
105
- '0.1 Mi': 104857,
106
- '0.1 Gi': 107374182,
107
- '0.1 Ti': 109951162777,
108
- '0.1 K': 100,
109
- '0.1 M': 100000,
110
- '0.1 G': 100000000,
111
- '0.1 T': 100000000000,
112
- '0.5 b': 0,
113
- '0.5 Ki': 512,
114
- '0.5 Mi': 524288,
115
- '0.5 Gi': 536870912,
116
- '0.5 Ti': 549755813888,
117
- '0.5 K': 500,
118
- '0.5 M': 500000,
119
- '0.5 G': 500000000,
120
- '0.5 T': 500000000000,
121
- '0.9 b': 0,
122
- '0.9 Ki': 921,
123
- '0.9 Mi': 943718,
124
- '0.9 Gi': 966367641,
125
- '0.9 Ti': 989560464998,
126
- '0.9 K': 900,
127
- '0.9 M': 900000,
128
- '0.9 G': 900000000,
129
- '0.9 T': 900000000000,
130
- '1 b': 1,
131
- '1 Ki': 1024,
132
- '1 Mi': 1048576,
133
- '1 Gi': 1073741824,
134
- '1 Ti': 1099511627776,
135
- '1 K': 1000,
136
- '1 M': 1000000,
137
- '1 G': 1000000000,
138
- '1 T': 1000000000000,
139
- '7 b': 7,
140
- '7 Ki': 7168,
141
- '7 Mi': 7340032,
142
- '7 Gi': 7516192768,
143
- '7 Ti': 7696581394432,
144
- '7 K': 7000,
145
- '7 M': 7000000,
146
- '7 G': 7000000000,
147
- '7 T': 7000000000000,
148
- '7.42423 b': 7,
149
- '7.42423 Ki': 7602,
150
- '7.42423 Mi': 7784869,
151
- '7.42423 Gi': 7971706261,
152
- '7.42423 Ti': 8163027212283,
153
- '7.42423 K': 7424,
154
- '7.42423 M': 7424230,
155
- '7.42423 G': 7424230000,
156
- '7.42423 T': 7424230000000,
157
- '10 b': 10,
158
- '10 Ki': 10240,
159
- '10 Mi': 10485760,
160
- '10 Gi': 10737418240,
161
- '10 Ti': 10995116277760,
162
- '10 K': 10000,
163
- '10 M': 10000000,
164
- '10 G': 10000000000,
165
- '10 T': 10000000000000,
166
- '100 b': 100,
167
- '100 Ki': 102400,
168
- '100 Mi': 104857600,
169
- '100 Gi': 107374182400,
170
- '100 Ti': 109951162777600,
171
- '100 K': 100000,
172
- '100 M': 100000000,
173
- '100 G': 100000000000,
174
- '100 T': 100000000000000,
175
- '1000 b': 1000,
176
- '1000 Ki': 1024000,
177
- '1000 Mi': 1048576000,
178
- '1000 Gi': 1073741824000,
179
- '1000 Ti': 1099511627776000,
180
- '1000 K': 1000000,
181
- '1000 M': 1000000000,
182
- '1000 G': 1000000000000,
183
- '1000 T': 1000000000000000,
184
- '11234234 b': 11234234,
185
- '11234234 Ki': 11503855616,
186
- '11234234 Mi': 11779948150784,
187
- '11234234 Gi': 12062666906402816,
188
- '11234234 Ti': 12352170912156483584,
189
- '11234234 K': 11234234000,
190
- '11234234 M': 11234234000000,
191
- '11234234 G': 11234234000000000,
192
- '11234234 T': 11234234000000000000
92
+ "0 b": 0,
93
+ "0 Ki": 0,
94
+ "0 Mi": 0,
95
+ "0 Gi": 0,
96
+ "0 Ti": 0,
97
+ "0 K": 0,
98
+ "0 M": 0,
99
+ "0 G": 0,
100
+ "0 T": 0,
101
+ "0.1 b": 0,
102
+ "0.1 Ki": 102,
103
+ "0.1 Mi": 104857,
104
+ "0.1 Gi": 107374182,
105
+ "0.1 Ti": 109951162777,
106
+ "0.1 K": 100,
107
+ "0.1 M": 100000,
108
+ "0.1 G": 100000000,
109
+ "0.1 T": 100000000000,
110
+ "0.5 b": 0,
111
+ "0.5 Ki": 512,
112
+ "0.5 Mi": 524288,
113
+ "0.5 Gi": 536870912,
114
+ "0.5 Ti": 549755813888,
115
+ "0.5 K": 500,
116
+ "0.5 M": 500000,
117
+ "0.5 G": 500000000,
118
+ "0.5 T": 500000000000,
119
+ "0.9 b": 0,
120
+ "0.9 Ki": 921,
121
+ "0.9 Mi": 943718,
122
+ "0.9 Gi": 966367641,
123
+ "0.9 Ti": 989560464998,
124
+ "0.9 K": 900,
125
+ "0.9 M": 900000,
126
+ "0.9 G": 900000000,
127
+ "0.9 T": 900000000000,
128
+ "1 b": 1,
129
+ "1 Ki": 1024,
130
+ "1 Mi": 1048576,
131
+ "1 Gi": 1073741824,
132
+ "1 Ti": 1099511627776,
133
+ "1 K": 1000,
134
+ "1 M": 1000000,
135
+ "1 G": 1000000000,
136
+ "1 T": 1000000000000,
137
+ "7 b": 7,
138
+ "7 Ki": 7168,
139
+ "7 Mi": 7340032,
140
+ "7 Gi": 7516192768,
141
+ "7 Ti": 7696581394432,
142
+ "7 K": 7000,
143
+ "7 M": 7000000,
144
+ "7 G": 7000000000,
145
+ "7 T": 7000000000000,
146
+ "7.42423 b": 7,
147
+ "7.42423 Ki": 7602,
148
+ "7.42423 Mi": 7784869,
149
+ "7.42423 Gi": 7971706261,
150
+ "7.42423 Ti": 8163027212283,
151
+ "7.42423 K": 7424,
152
+ "7.42423 M": 7424230,
153
+ "7.42423 G": 7424230000,
154
+ "7.42423 T": 7424230000000,
155
+ "10 b": 10,
156
+ "10 Ki": 10240,
157
+ "10 Mi": 10485760,
158
+ "10 Gi": 10737418240,
159
+ "10 Ti": 10995116277760,
160
+ "10 K": 10000,
161
+ "10 M": 10000000,
162
+ "10 G": 10000000000,
163
+ "10 T": 10000000000000,
164
+ "100 b": 100,
165
+ "100 Ki": 102400,
166
+ "100 Mi": 104857600,
167
+ "100 Gi": 107374182400,
168
+ "100 Ti": 109951162777600,
169
+ "100 K": 100000,
170
+ "100 M": 100000000,
171
+ "100 G": 100000000000,
172
+ "100 T": 100000000000000,
173
+ "1000 b": 1000,
174
+ "1000 Ki": 1024000,
175
+ "1000 Mi": 1048576000,
176
+ "1000 Gi": 1073741824000,
177
+ "1000 Ti": 1099511627776000,
178
+ "1000 K": 1000000,
179
+ "1000 M": 1000000000,
180
+ "1000 G": 1000000000000,
181
+ "1000 T": 1000000000000000,
182
+ "11234234 b": 11234234,
183
+ "11234234 Ki": 11503855616,
184
+ "11234234 Mi": 11779948150784,
185
+ "11234234 Gi": 12062666906402816,
186
+ "11234234 Ti": 12352170912156483584,
187
+ "11234234 K": 11234234000,
188
+ "11234234 M": 11234234000000,
189
+ "11234234 G": 11234234000000000,
190
+ "11234234 T": 11234234000000000000,
193
191
  }
194
192
 
195
193
  results = {}
196
194
  for i in (0, 0.1, 0.5, 0.9, 1, 7, 7.42423, 10, 100, 1000, 11234234):
197
- for src_unit in ['b', 'Ki', 'Mi', 'Gi', 'Ti', 'K', 'M', 'G', 'T']:
198
- results[f'{i} {src_unit}'] = human2bytes(f'{i} {src_unit}')
195
+ for src_unit in ["b", "Ki", "Mi", "Gi", "Ti", "K", "M", "G", "T"]:
196
+ results[f"{i} {src_unit}"] = human2bytes(f"{i} {src_unit}")
199
197
  self.assertEqual(results, expected_results)
200
198
 
201
199
  def test_hms_duration_to_seconds(self):
202
200
  expected_results = {
203
- '0:0:0' : 0.0,
204
- '00:00:00' : 0.0,
205
- '1:1:1' : 3661.0,
206
- '20:14:33' : 72873.0,
207
- '72:80:112' : 264112.0,
201
+ "0:0:0": 0.0,
202
+ "00:00:00": 0.0,
203
+ "1:1:1": 3661.0,
204
+ "20:14:33": 72873.0,
205
+ "72:80:112": 264112.0,
208
206
  }
209
207
  results = {}
210
208
  for key in expected_results.keys():
211
- results[key] = hms_duration_to_seconds(f'{key}')
212
-
209
+ results[key] = hms_duration_to_seconds(f"{key}")
210
+
213
211
  self.assertEqual(results, expected_results)
toil/test/lib/test_ec2.py CHANGED
@@ -14,17 +14,21 @@
14
14
  import logging
15
15
  import os
16
16
 
17
- import pytest
17
+ from unittest import mock
18
18
 
19
- from toil.lib.aws.ami import (aws_marketplace_flatcar_ami_search,
20
- feed_flatcar_ami_release,
21
- flatcar_release_feed_amis,
22
- get_flatcar_ami)
19
+ from toil.lib.aws.ami import (
20
+ aws_marketplace_flatcar_ami_search,
21
+ feed_flatcar_ami_release,
22
+ flatcar_release_feed_ami,
23
+ get_flatcar_ami,
24
+ ReleaseFeedUnavailableError
25
+ )
23
26
  from toil.test import ToilTest, needs_aws_ec2, needs_online
24
27
 
25
28
  logger = logging.getLogger(__name__)
26
29
  logging.basicConfig(level=logging.DEBUG)
27
30
 
31
+
28
32
  @needs_online
29
33
  class FlatcarFeedTest(ToilTest):
30
34
  """Test accessing the Flatcar AMI release feed, independent of the AWS API"""
@@ -35,62 +39,66 @@ class FlatcarFeedTest(ToilTest):
35
39
 
36
40
  def test_parse_archive_feed(self):
37
41
  """Make sure we can get a Flatcar release from the Internet Archive."""
38
- amis = list(flatcar_release_feed_amis('us-west-2', 'amd64', 'archive'))
39
- for ami in amis:
40
- self.assertEqual(len(ami), len('ami-02b46c73fed689d1c'))
41
- self.assertTrue(ami.startswith('ami-'))
42
-
42
+ ami = flatcar_release_feed_ami("us-west-2", "amd64", "archive")
43
+ self.assertEqual(len(ami), len("ami-02b46c73fed689d1c"))
44
+ self.assertTrue(ami.startswith("ami-"))
45
+
43
46
  def test_parse_beta_feed(self):
44
47
  """Make sure we can get a Flatcar release from the beta channel."""
45
- amis = list(flatcar_release_feed_amis('us-west-2', 'amd64', 'beta'))
46
- for ami in amis:
47
- self.assertEqual(len(ami), len('ami-02b46c73fed689d1c'))
48
- self.assertTrue(ami.startswith('ami-'))
49
-
48
+ ami = flatcar_release_feed_ami("us-west-2", "amd64", "beta")
49
+ self.assertEqual(len(ami), len("ami-02b46c73fed689d1c"))
50
+ self.assertTrue(ami.startswith("ami-"))
51
+
50
52
  def test_parse_stable_feed(self):
51
53
  """Make sure we can get a Flatcar release from the stable channel."""
52
- amis = list(flatcar_release_feed_amis('us-west-2', 'amd64', 'stable'))
53
- for ami in amis:
54
- self.assertEqual(len(ami), len('ami-02b46c73fed689d1c'))
55
- self.assertTrue(ami.startswith('ami-'))
56
-
54
+ ami = flatcar_release_feed_ami("us-west-2", "amd64", "stable")
55
+ self.assertEqual(len(ami), len("ami-02b46c73fed689d1c"))
56
+ self.assertTrue(ami.startswith("ami-"))
57
+
58
+
57
59
  @needs_aws_ec2
58
60
  class AMITest(ToilTest):
59
61
  @classmethod
60
62
  def setUpClass(cls):
61
63
  from toil.lib.aws.session import establish_boto3_session
62
- session = establish_boto3_session(region_name='us-west-2')
63
- cls.ec2_client = session.client('ec2')
64
+
65
+ session = establish_boto3_session(region_name="us-west-2")
66
+ cls.ec2_client = session.client("ec2")
64
67
 
65
68
  def test_fetch_flatcar(self):
66
- with self.subTest('Test flatcar AMI from user is prioritized.'):
67
- os.environ['TOIL_AWS_AMI'] = 'overridden'
68
- ami = get_flatcar_ami(self.ec2_client)
69
- self.assertEqual(ami, 'overridden')
70
- del os.environ['TOIL_AWS_AMI']
71
-
72
- with self.subTest('Test flatcar AMI returns an AMI-looking AMI.'):
73
- ami = get_flatcar_ami(self.ec2_client)
74
- self.assertEqual(len(ami), len('ami-02b46c73fed689d1c'))
75
- self.assertTrue(ami.startswith('ami-'))
76
-
77
- with self.subTest('Test feed_flatcar_ami_release() returns an AMI-looking AMI.'):
78
- ami = feed_flatcar_ami_release(self.ec2_client, source='archive')
79
- self.assertTrue(ami is None or len(ami) == len('ami-02b46c73fed689d1c'))
80
- self.assertTrue(ami is None or ami.startswith('ami-'))
81
-
82
- with self.subTest('Test aws_marketplace_flatcar_ami_search() returns an AMI-looking AMI.'):
69
+ with self.subTest("Test flatcar AMI from user is prioritized."):
70
+ with mock.patch.dict(os.environ, {"TOIL_AWS_AMI": "overridden"}):
71
+ ami = get_flatcar_ami(self.ec2_client)
72
+ self.assertEqual(ami, "overridden")
73
+
74
+ with self.subTest("Test flatcar AMI returns an AMI-looking AMI."):
75
+ try:
76
+ ami = get_flatcar_ami(self.ec2_client)
77
+ self.assertEqual(len(ami), len("ami-02b46c73fed689d1c"))
78
+ self.assertTrue(ami.startswith("ami-"))
79
+ except ReleaseFeedUnavailableError:
80
+ # Ignore any remote systems being down.
81
+ pass
82
+
83
+ with self.subTest(
84
+ "Test feed_flatcar_ami_release() returns an AMI-looking AMI."
85
+ ):
86
+ ami = feed_flatcar_ami_release(self.ec2_client, source="archive")
87
+ self.assertTrue(ami is None or len(ami) == len("ami-02b46c73fed689d1c"))
88
+ self.assertTrue(ami is None or ami.startswith("ami-"))
89
+
90
+ with self.subTest(
91
+ "Test aws_marketplace_flatcar_ami_search() returns an AMI-looking AMI."
92
+ ):
83
93
  ami = aws_marketplace_flatcar_ami_search(self.ec2_client)
84
- self.assertEqual(len(ami), len('ami-02b46c73fed689d1c'))
85
- self.assertTrue(ami.startswith('ami-'))
94
+ self.assertTrue(ami is None or len(ami), len("ami-02b46c73fed689d1c"))
95
+ self.assertTrue(ami is None or ami.startswith("ami-"))
86
96
 
87
- # TODO: This will fail until https://github.com/flatcar/Flatcar/issues/962 is fixed
88
- @pytest.mark.xfail
89
97
  def test_fetch_arm_flatcar(self):
90
98
  """Test flatcar AMI finder architecture parameter."""
91
- amis = set()
92
- for arch in ['amd64', 'arm64']:
93
- ami = get_flatcar_ami(self.ec2_client, architecture=arch)
94
- self.assertTrue(ami.startswith('ami-'))
95
- amis.add(ami)
96
- self.assertTrue(len(amis) == 2)
99
+ try:
100
+ ami = get_flatcar_ami(self.ec2_client, architecture="arm64")
101
+ self.assertTrue(ami.startswith("ami-"))
102
+ except ReleaseFeedUnavailableError:
103
+ # Ignore any remote systems being down.
104
+ pass
@@ -0,0 +1,212 @@
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
+
15
+ import os
16
+ import logging
17
+ import pytest
18
+ import time
19
+ from toil.test import ToilTest
20
+
21
+ from toil.lib.history import HistoryManager
22
+
23
+ logger = logging.getLogger(__name__)
24
+ logging.basicConfig(level=logging.DEBUG)
25
+
26
+ class HistoryTest(ToilTest):
27
+ """
28
+ Tests for Toil history tracking.
29
+
30
+ Each test gets its own history database.
31
+ """
32
+
33
+ def setUp(self) -> None:
34
+ super().setUp()
35
+
36
+ # Apply a temp dir override to history tracking
37
+ temp_dir = self._createTempDir()
38
+ HistoryManager.database_path_override = os.path.join(temp_dir, "test-db.sqlite")
39
+
40
+ # Flag on job history tracking
41
+ self.original_flag = HistoryManager.JOB_HISTORY_ENABLED
42
+ HistoryManager.JOB_HISTORY_ENABLED = True
43
+
44
+
45
+ def tearDown(self) -> None:
46
+ # Remove the temp dir override from history tracking
47
+ HistoryManager.database_path_override = None
48
+
49
+ # Restore job history tracking flag
50
+ HistoryManager.JOB_HISTORY_ENABLED = self.original_flag
51
+
52
+ super().tearDown()
53
+
54
+ def make_fake_workflow(self, workflow_id: str) -> None:
55
+ # Make a fake workflow
56
+ workflow_jobstore_spec = "file:/tmp/tree"
57
+ HistoryManager.record_workflow_creation(workflow_id, workflow_jobstore_spec)
58
+ workflow_name = "SuperCoolWF"
59
+ workflow_trs_spec = "#wf:v1"
60
+ HistoryManager.record_workflow_metadata(workflow_id, workflow_name, workflow_trs_spec)
61
+
62
+ # Give it a job
63
+ workflow_attempt_number = 1
64
+ job_name = "DoThing"
65
+ succeeded = True
66
+ start_time = time.time()
67
+ runtime = 0.1
68
+ HistoryManager.record_job_attempt(
69
+ workflow_id,
70
+ workflow_attempt_number,
71
+ job_name,
72
+ succeeded,
73
+ start_time,
74
+ runtime,
75
+ )
76
+
77
+ # Give it a workflow attempt with the same details.
78
+ HistoryManager.record_workflow_attempt(
79
+ workflow_id,
80
+ workflow_attempt_number,
81
+ succeeded,
82
+ start_time,
83
+ runtime,
84
+ )
85
+
86
+ def test_history_submittable_detection(self) -> None:
87
+ """
88
+ Make sure that a submittable workflow shows up as such before
89
+ submission and doesn't afterward.
90
+ """
91
+ workflow_id = "123"
92
+ self.make_fake_workflow(workflow_id)
93
+ workflow_attempt_number = 1
94
+
95
+ # Make sure we have data
96
+ self.assertEqual(HistoryManager.count_workflows(), 1)
97
+ self.assertEqual(HistoryManager.count_workflow_attempts(), 1)
98
+ self.assertEqual(HistoryManager.count_job_attempts(), 1)
99
+
100
+ # Make sure we see it as submittable
101
+ submittable_workflow_attempts = HistoryManager.get_submittable_workflow_attempts()
102
+ self.assertEqual(len(submittable_workflow_attempts), 1)
103
+
104
+ # Make sure we see its jobs as submittable
105
+ with_submittable_job_attempts = HistoryManager.get_workflow_attempts_with_submittable_job_attempts()
106
+ self.assertEqual(len(with_submittable_job_attempts), 1)
107
+
108
+ # Make sure we actually see the job
109
+ submittable_job_attempts = HistoryManager.get_unsubmitted_job_attempts(workflow_id, workflow_attempt_number)
110
+ self.assertEqual(len(submittable_job_attempts), 1)
111
+
112
+ # Pretend we submitted them.
113
+ HistoryManager.mark_job_attempts_submitted([j.id for j in submittable_job_attempts])
114
+ HistoryManager.mark_workflow_attempt_submitted(workflow_id, workflow_attempt_number)
115
+
116
+ # Make sure they are no longer matching
117
+ self.assertEqual(len(HistoryManager.get_submittable_workflow_attempts()), 0)
118
+ self.assertEqual(len(HistoryManager.get_workflow_attempts_with_submittable_job_attempts()), 0)
119
+ self.assertEqual(len(HistoryManager.get_unsubmitted_job_attempts(workflow_id, workflow_attempt_number)), 0)
120
+
121
+ # Make sure we still have data
122
+ self.assertEqual(HistoryManager.count_workflows(), 1)
123
+ self.assertEqual(HistoryManager.count_workflow_attempts(), 1)
124
+ self.assertEqual(HistoryManager.count_job_attempts(), 1)
125
+
126
+ def test_history_deletion(self) -> None:
127
+ workflow_id = "123"
128
+ self.make_fake_workflow(workflow_id)
129
+ workflow_attempt_number = 1
130
+
131
+ # Make sure we can see the workflow for deletion by age but not by done-ness
132
+ self.assertEqual(len(HistoryManager.get_oldest_workflow_ids()), 1)
133
+ self.assertEqual(len(HistoryManager.get_fully_submitted_workflow_ids()), 0)
134
+
135
+ # Pretend we submitted the workflow.
136
+ HistoryManager.mark_job_attempts_submitted([j.id for j in HistoryManager.get_unsubmitted_job_attempts(workflow_id, workflow_attempt_number)])
137
+ HistoryManager.mark_workflow_attempt_submitted(workflow_id, workflow_attempt_number)
138
+
139
+ # Make sure we can see the workflow for deletion by done-ness
140
+ self.assertEqual(len(HistoryManager.get_fully_submitted_workflow_ids()), 1)
141
+
142
+ # Add a new workflow
143
+ other_workflow_id = "456"
144
+ self.make_fake_workflow(other_workflow_id)
145
+
146
+ # Make sure we can see the both for deletion by age but only one by done-ness
147
+ self.assertEqual(len(HistoryManager.get_oldest_workflow_ids()), 2)
148
+ self.assertEqual(len(HistoryManager.get_fully_submitted_workflow_ids()), 1)
149
+
150
+ # Make sure the older workflow is first.
151
+ self.assertEqual(HistoryManager.get_oldest_workflow_ids(), [workflow_id, other_workflow_id])
152
+
153
+ # Delete the new workflow
154
+ HistoryManager.delete_workflow(other_workflow_id)
155
+
156
+ # Make sure we can see the old one
157
+ self.assertEqual(HistoryManager.get_oldest_workflow_ids(), [workflow_id])
158
+ self.assertEqual(HistoryManager.get_fully_submitted_workflow_ids(), [workflow_id])
159
+
160
+ # Delete the old workflow
161
+ HistoryManager.delete_workflow(workflow_id)
162
+
163
+ # Make sure we have no data
164
+ self.assertEqual(HistoryManager.count_workflows(), 0)
165
+ self.assertEqual(HistoryManager.count_workflow_attempts(), 0)
166
+ self.assertEqual(HistoryManager.count_job_attempts(), 0)
167
+
168
+
169
+ def test_history_size_limit(self) -> None:
170
+ """
171
+ Make sure the database size can be controlled.
172
+ """
173
+
174
+ for workflow_id in ("WorkflowThatTakesUpSomeSpace,ActuallyMoreThanTheLaterOnesTake" + str(i) for i in range(10)):
175
+ self.make_fake_workflow(workflow_id)
176
+
177
+ # We should see the workflows.
178
+ self.assertEqual(HistoryManager.count_workflows(), 10)
179
+ # And they take up space.
180
+ small_size = HistoryManager.get_database_byte_size()
181
+ self.assertGreater(small_size, 0)
182
+
183
+ # Add a bunch more
184
+ for workflow_id in ("WorkflowThatTakesUpSpace" + str(i) for i in range(50)):
185
+ self.make_fake_workflow(workflow_id)
186
+
187
+ # We should see that this is now a much larger database
188
+ large_size = HistoryManager.get_database_byte_size()
189
+ logger.info("Increased database size from %s to %s", small_size, large_size)
190
+ self.assertGreater(large_size, small_size)
191
+
192
+ # We should be able to shrink it back down
193
+ HistoryManager.enforce_byte_size_limit(small_size)
194
+
195
+ reduced_size = HistoryManager.get_database_byte_size()
196
+ logger.info("Decreased database size from %s to %s", large_size, reduced_size)
197
+ # The database should be small enough
198
+ self.assertLessEqual(reduced_size, small_size)
199
+ # There should still be some workflow attempts left in the smaller database (though probably not the first ones)
200
+ remaining_workflows = HistoryManager.count_workflows()
201
+ logger.info("Still have %s workflows", remaining_workflows)
202
+ self.assertGreater(remaining_workflows, 0)
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+