toil 6.1.0a1__py3-none-any.whl → 8.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 +122 -315
- toil/batchSystems/__init__.py +1 -0
- toil/batchSystems/abstractBatchSystem.py +173 -89
- toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
- toil/batchSystems/awsBatch.py +244 -135
- toil/batchSystems/cleanup_support.py +26 -16
- toil/batchSystems/contained_executor.py +31 -28
- toil/batchSystems/gridengine.py +86 -50
- toil/batchSystems/htcondor.py +166 -89
- toil/batchSystems/kubernetes.py +632 -382
- toil/batchSystems/local_support.py +20 -15
- toil/batchSystems/lsf.py +134 -81
- toil/batchSystems/lsfHelper.py +13 -11
- toil/batchSystems/mesos/__init__.py +41 -29
- toil/batchSystems/mesos/batchSystem.py +290 -151
- toil/batchSystems/mesos/executor.py +79 -50
- toil/batchSystems/mesos/test/__init__.py +31 -23
- toil/batchSystems/options.py +46 -28
- toil/batchSystems/registry.py +53 -19
- toil/batchSystems/singleMachine.py +296 -125
- toil/batchSystems/slurm.py +603 -138
- toil/batchSystems/torque.py +47 -33
- toil/bus.py +186 -76
- toil/common.py +664 -368
- toil/cwl/__init__.py +1 -1
- toil/cwl/cwltoil.py +1136 -483
- toil/cwl/utils.py +17 -22
- toil/deferred.py +63 -42
- toil/exceptions.py +5 -3
- toil/fileStores/__init__.py +5 -5
- toil/fileStores/abstractFileStore.py +140 -60
- toil/fileStores/cachingFileStore.py +717 -269
- toil/fileStores/nonCachingFileStore.py +116 -87
- toil/job.py +1225 -368
- toil/jobStores/abstractJobStore.py +416 -266
- toil/jobStores/aws/jobStore.py +863 -477
- toil/jobStores/aws/utils.py +201 -120
- toil/jobStores/conftest.py +3 -2
- toil/jobStores/fileJobStore.py +292 -154
- toil/jobStores/googleJobStore.py +140 -74
- toil/jobStores/utils.py +36 -15
- toil/leader.py +668 -272
- toil/lib/accelerators.py +115 -18
- toil/lib/aws/__init__.py +74 -31
- toil/lib/aws/ami.py +122 -87
- toil/lib/aws/iam.py +284 -108
- toil/lib/aws/s3.py +31 -0
- toil/lib/aws/session.py +214 -39
- toil/lib/aws/utils.py +287 -231
- toil/lib/bioio.py +13 -5
- toil/lib/compatibility.py +11 -6
- toil/lib/conversions.py +104 -47
- toil/lib/docker.py +131 -103
- toil/lib/ec2.py +361 -199
- toil/lib/ec2nodes.py +174 -106
- toil/lib/encryption/_dummy.py +5 -3
- toil/lib/encryption/_nacl.py +10 -6
- toil/lib/encryption/conftest.py +1 -0
- toil/lib/exceptions.py +26 -7
- toil/lib/expando.py +5 -3
- toil/lib/ftp_utils.py +217 -0
- toil/lib/generatedEC2Lists.py +127 -19
- toil/lib/humanize.py +6 -2
- toil/lib/integration.py +341 -0
- toil/lib/io.py +141 -15
- toil/lib/iterables.py +4 -2
- toil/lib/memoize.py +12 -8
- toil/lib/misc.py +66 -21
- toil/lib/objects.py +2 -2
- toil/lib/resources.py +68 -15
- toil/lib/retry.py +126 -81
- toil/lib/threading.py +299 -82
- toil/lib/throttle.py +16 -15
- toil/options/common.py +843 -409
- toil/options/cwl.py +175 -90
- toil/options/runner.py +50 -0
- toil/options/wdl.py +73 -17
- toil/provisioners/__init__.py +117 -46
- toil/provisioners/abstractProvisioner.py +332 -157
- toil/provisioners/aws/__init__.py +70 -33
- toil/provisioners/aws/awsProvisioner.py +1145 -715
- toil/provisioners/clusterScaler.py +541 -279
- toil/provisioners/gceProvisioner.py +282 -179
- toil/provisioners/node.py +155 -79
- toil/realtimeLogger.py +34 -22
- toil/resource.py +137 -75
- toil/server/app.py +128 -62
- toil/server/celery_app.py +3 -1
- toil/server/cli/wes_cwl_runner.py +82 -53
- toil/server/utils.py +54 -28
- toil/server/wes/abstract_backend.py +64 -26
- toil/server/wes/amazon_wes_utils.py +21 -15
- toil/server/wes/tasks.py +121 -63
- toil/server/wes/toil_backend.py +142 -107
- toil/server/wsgi_app.py +4 -3
- toil/serviceManager.py +58 -22
- toil/statsAndLogging.py +224 -70
- toil/test/__init__.py +282 -183
- toil/test/batchSystems/batchSystemTest.py +460 -210
- toil/test/batchSystems/batch_system_plugin_test.py +90 -0
- toil/test/batchSystems/test_gridengine.py +173 -0
- toil/test/batchSystems/test_lsf_helper.py +67 -58
- toil/test/batchSystems/test_slurm.py +110 -49
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +56 -0
- toil/test/cwl/cwlTest.py +496 -287
- toil/test/cwl/measure_default_memory.cwl +12 -0
- toil/test/cwl/not_run_required_input.cwl +29 -0
- toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
- toil/test/cwl/seqtk_seq.cwl +1 -1
- toil/test/docs/scriptsTest.py +69 -46
- toil/test/jobStores/jobStoreTest.py +427 -264
- toil/test/lib/aws/test_iam.py +118 -50
- toil/test/lib/aws/test_s3.py +16 -9
- toil/test/lib/aws/test_utils.py +5 -6
- toil/test/lib/dockerTest.py +118 -141
- toil/test/lib/test_conversions.py +113 -115
- toil/test/lib/test_ec2.py +58 -50
- toil/test/lib/test_integration.py +104 -0
- toil/test/lib/test_misc.py +12 -5
- toil/test/mesos/MesosDataStructuresTest.py +23 -10
- toil/test/mesos/helloWorld.py +7 -6
- toil/test/mesos/stress.py +25 -20
- toil/test/options/__init__.py +13 -0
- toil/test/options/options.py +42 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
- toil/test/provisioners/clusterScalerTest.py +440 -250
- toil/test/provisioners/clusterTest.py +166 -44
- toil/test/provisioners/gceProvisionerTest.py +174 -100
- toil/test/provisioners/provisionerTest.py +25 -13
- toil/test/provisioners/restartScript.py +5 -4
- toil/test/server/serverTest.py +188 -141
- toil/test/sort/restart_sort.py +137 -68
- toil/test/sort/sort.py +134 -66
- toil/test/sort/sortTest.py +91 -49
- toil/test/src/autoDeploymentTest.py +141 -101
- toil/test/src/busTest.py +20 -18
- toil/test/src/checkpointTest.py +8 -2
- toil/test/src/deferredFunctionTest.py +49 -35
- toil/test/src/dockerCheckTest.py +32 -24
- toil/test/src/environmentTest.py +135 -0
- toil/test/src/fileStoreTest.py +539 -272
- toil/test/src/helloWorldTest.py +7 -4
- toil/test/src/importExportFileTest.py +61 -31
- toil/test/src/jobDescriptionTest.py +46 -21
- toil/test/src/jobEncapsulationTest.py +2 -0
- toil/test/src/jobFileStoreTest.py +74 -50
- toil/test/src/jobServiceTest.py +187 -73
- toil/test/src/jobTest.py +121 -71
- toil/test/src/miscTests.py +19 -18
- toil/test/src/promisedRequirementTest.py +82 -36
- toil/test/src/promisesTest.py +7 -6
- toil/test/src/realtimeLoggerTest.py +10 -6
- toil/test/src/regularLogTest.py +71 -37
- toil/test/src/resourceTest.py +80 -49
- toil/test/src/restartDAGTest.py +36 -22
- toil/test/src/resumabilityTest.py +9 -2
- toil/test/src/retainTempDirTest.py +45 -14
- toil/test/src/systemTest.py +12 -8
- toil/test/src/threadingTest.py +44 -25
- toil/test/src/toilContextManagerTest.py +10 -7
- toil/test/src/userDefinedJobArgTypeTest.py +8 -5
- toil/test/src/workerTest.py +73 -23
- toil/test/utils/toilDebugTest.py +103 -33
- toil/test/utils/toilKillTest.py +4 -5
- toil/test/utils/utilsTest.py +245 -106
- toil/test/wdl/wdltoil_test.py +818 -149
- toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
- toil/toilState.py +120 -35
- toil/utils/toilConfig.py +13 -4
- toil/utils/toilDebugFile.py +44 -27
- toil/utils/toilDebugJob.py +214 -27
- toil/utils/toilDestroyCluster.py +11 -6
- toil/utils/toilKill.py +8 -3
- toil/utils/toilLaunchCluster.py +256 -140
- toil/utils/toilMain.py +37 -16
- toil/utils/toilRsyncCluster.py +32 -14
- toil/utils/toilSshCluster.py +49 -22
- toil/utils/toilStats.py +356 -273
- toil/utils/toilStatus.py +292 -139
- toil/utils/toilUpdateEC2Instances.py +3 -1
- toil/version.py +12 -12
- toil/wdl/utils.py +5 -5
- toil/wdl/wdltoil.py +3913 -1033
- toil/worker.py +367 -184
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
- toil-8.0.0.dist-info/METADATA +173 -0
- toil-8.0.0.dist-info/RECORD +253 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
- toil-6.1.0a1.dist-info/METADATA +0 -125
- toil-6.1.0a1.dist-info/RECORD +0 -237
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/lib/ec2nodes.py
CHANGED
|
@@ -16,74 +16,90 @@ import json
|
|
|
16
16
|
import logging
|
|
17
17
|
import os
|
|
18
18
|
import re
|
|
19
|
-
import textwrap
|
|
20
|
-
import requests
|
|
21
19
|
import shutil
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
from typing import Dict, List, Tuple, Union, Any
|
|
20
|
+
import textwrap
|
|
21
|
+
from typing import Any, Union
|
|
25
22
|
|
|
23
|
+
import enlighten # type: ignore
|
|
24
|
+
import requests
|
|
26
25
|
|
|
27
26
|
logger = logging.getLogger(__name__)
|
|
28
27
|
manager = enlighten.get_manager()
|
|
29
28
|
dirname = os.path.dirname(__file__)
|
|
30
|
-
region_json_dirname = os.path.join(dirname,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
EC2Regions = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
29
|
+
region_json_dirname = os.path.join(dirname, "region_jsons")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
EC2Regions = {
|
|
33
|
+
"us-west-1": "US West (N. California)",
|
|
34
|
+
"us-west-2": "US West (Oregon)",
|
|
35
|
+
"us-east-1": "US East (N. Virginia)",
|
|
36
|
+
"us-east-2": "US East (Ohio)",
|
|
37
|
+
"us-gov-west-1": "AWS GovCloud (US)",
|
|
38
|
+
"ca-central-1": "Canada (Central)",
|
|
39
|
+
"ap-northeast-1": "Asia Pacific (Tokyo)",
|
|
40
|
+
"ap-northeast-2": "Asia Pacific (Seoul)",
|
|
41
|
+
"ap-northeast-3": "Asia Pacific (Osaka-Local)",
|
|
42
|
+
"ap-southeast-1": "Asia Pacific (Singapore)",
|
|
43
|
+
"ap-southeast-2": "Asia Pacific (Sydney)",
|
|
44
|
+
"ap-south-1": "Asia Pacific (Mumbai)",
|
|
45
|
+
"eu-west-1": "EU (Ireland)",
|
|
46
|
+
"eu-west-2": "EU (London)",
|
|
47
|
+
"eu-west-3": "EU (Paris)",
|
|
48
|
+
"eu-central-1": "EU (Frankfurt)",
|
|
49
|
+
"sa-east-1": "South America (Sao Paulo)",
|
|
50
|
+
}
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
class InstanceType:
|
|
53
|
-
__slots__ = (
|
|
54
|
-
|
|
55
|
-
def __init__(
|
|
54
|
+
__slots__ = ("name", "cores", "memory", "disks", "disk_capacity", "architecture")
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
name: str,
|
|
59
|
+
cores: int,
|
|
60
|
+
memory: float,
|
|
61
|
+
disks: float,
|
|
62
|
+
disk_capacity: float,
|
|
63
|
+
architecture: str,
|
|
64
|
+
):
|
|
56
65
|
self.name = name # the API name of the instance type
|
|
57
66
|
self.cores = cores # the number of cores
|
|
58
67
|
self.memory = memory # RAM in GiB
|
|
59
68
|
self.disks = disks # the number of ephemeral (aka 'instance store') volumes
|
|
60
|
-
self.disk_capacity =
|
|
61
|
-
|
|
69
|
+
self.disk_capacity = (
|
|
70
|
+
disk_capacity # the capacity of each ephemeral volume in GiB
|
|
71
|
+
)
|
|
72
|
+
self.architecture = architecture # the architecture of the instance type. Can be either amd64 or arm64
|
|
62
73
|
|
|
63
74
|
def __str__(self) -> str:
|
|
64
|
-
return (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
return (
|
|
76
|
+
"Type: {}\n"
|
|
77
|
+
"Cores: {}\n"
|
|
78
|
+
"Disks: {}\n"
|
|
79
|
+
"Memory: {}\n"
|
|
80
|
+
"Disk Capacity: {}\n"
|
|
81
|
+
"Architecture: {}\n"
|
|
82
|
+
"".format(
|
|
71
83
|
self.name,
|
|
72
84
|
self.cores,
|
|
73
85
|
self.disks,
|
|
74
86
|
self.memory,
|
|
75
87
|
self.disk_capacity,
|
|
76
|
-
self.architecture
|
|
88
|
+
self.architecture,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
77
91
|
|
|
78
92
|
def __eq__(self, other: object) -> bool:
|
|
79
93
|
if not isinstance(other, InstanceType):
|
|
80
94
|
return NotImplemented
|
|
81
|
-
if (
|
|
82
|
-
self.
|
|
83
|
-
self.
|
|
84
|
-
self.
|
|
85
|
-
self.
|
|
86
|
-
self.
|
|
95
|
+
if (
|
|
96
|
+
self.name == other.name
|
|
97
|
+
and self.cores == other.cores
|
|
98
|
+
and self.memory == other.memory
|
|
99
|
+
and self.disks == other.disks
|
|
100
|
+
and self.disk_capacity == other.disk_capacity
|
|
101
|
+
and self.architecture == other.architecture
|
|
102
|
+
):
|
|
87
103
|
return True
|
|
88
104
|
return False
|
|
89
105
|
|
|
@@ -95,7 +111,7 @@ def is_number(s: str) -> bool:
|
|
|
95
111
|
:param s: Any unicode string.
|
|
96
112
|
:return: True if s represents a number, False otherwise.
|
|
97
113
|
"""
|
|
98
|
-
s = s.replace(
|
|
114
|
+
s = s.replace(",", "")
|
|
99
115
|
try:
|
|
100
116
|
float(s)
|
|
101
117
|
return True
|
|
@@ -103,6 +119,7 @@ def is_number(s: str) -> bool:
|
|
|
103
119
|
pass
|
|
104
120
|
try:
|
|
105
121
|
import unicodedata
|
|
122
|
+
|
|
106
123
|
unicodedata.numeric(s)
|
|
107
124
|
return True
|
|
108
125
|
except (TypeError, ValueError) as e:
|
|
@@ -110,7 +127,9 @@ def is_number(s: str) -> bool:
|
|
|
110
127
|
return False
|
|
111
128
|
|
|
112
129
|
|
|
113
|
-
def parse_storage(
|
|
130
|
+
def parse_storage(
|
|
131
|
+
storage_info: str,
|
|
132
|
+
) -> Union[list[int], tuple[Union[int, float], float]]:
|
|
114
133
|
"""
|
|
115
134
|
Parses EC2 JSON storage param string into a number.
|
|
116
135
|
|
|
@@ -129,12 +148,21 @@ def parse_storage(storage_info: str) -> Union[List[int], Tuple[Union[int, float]
|
|
|
129
148
|
return [0, 0]
|
|
130
149
|
else:
|
|
131
150
|
specs = storage_info.strip().split()
|
|
132
|
-
if is_number(specs[0]) and specs[1] ==
|
|
133
|
-
return float(specs[0].replace(
|
|
134
|
-
elif
|
|
135
|
-
|
|
151
|
+
if is_number(specs[0]) and specs[1].lower() == "x" and is_number(specs[2]):
|
|
152
|
+
return float(specs[0].replace(",", "")), float(specs[2].replace(",", ""))
|
|
153
|
+
elif (
|
|
154
|
+
is_number(specs[0])
|
|
155
|
+
and specs[1] == "GB"
|
|
156
|
+
and specs[2] == "NVMe"
|
|
157
|
+
and specs[3] == "SSD"
|
|
158
|
+
):
|
|
159
|
+
return 1, float(specs[0].replace(",", ""))
|
|
160
|
+
elif is_number(specs[0]) and specs[1].lower() == "x" and is_number(specs[2][:-2]) and specs[2][-2:] == "GB":
|
|
161
|
+
return float(specs[0].replace(",", "")), float(specs[2][:-2].replace(",", ""))
|
|
136
162
|
else:
|
|
137
|
-
raise RuntimeError(
|
|
163
|
+
raise RuntimeError(
|
|
164
|
+
f"EC2 JSON format has likely changed. Error parsing disk specs : {storage_info.strip()}"
|
|
165
|
+
)
|
|
138
166
|
|
|
139
167
|
|
|
140
168
|
def parse_memory(mem_info: str) -> float:
|
|
@@ -148,14 +176,14 @@ def parse_memory(mem_info: str) -> float:
|
|
|
148
176
|
:param mem_info: EC2 JSON memory param string.
|
|
149
177
|
:return: A float representing memory in GiB.
|
|
150
178
|
"""
|
|
151
|
-
mem = mem_info.replace(
|
|
152
|
-
if mem[1] ==
|
|
179
|
+
mem = mem_info.replace(",", "").split()
|
|
180
|
+
if mem[1] == "GiB":
|
|
153
181
|
return float(mem[0])
|
|
154
182
|
else:
|
|
155
|
-
raise RuntimeError(
|
|
183
|
+
raise RuntimeError("EC2 JSON format has likely changed. Error parsing memory.")
|
|
156
184
|
|
|
157
185
|
|
|
158
|
-
def download_region_json(filename: str, region: str =
|
|
186
|
+
def download_region_json(filename: str, region: str = "us-east-1") -> None:
|
|
159
187
|
"""
|
|
160
188
|
Downloads and writes the AWS Billing JSON to a file using the AWS pricing API.
|
|
161
189
|
|
|
@@ -165,18 +193,25 @@ def download_region_json(filename: str, region: str = 'us-east-1') -> None:
|
|
|
165
193
|
aws instance name (example: 't2.micro'), and the value is an
|
|
166
194
|
InstanceType object representing that aws instance name.
|
|
167
195
|
"""
|
|
168
|
-
response = requests.get(
|
|
196
|
+
response = requests.get(
|
|
197
|
+
f"https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/{region}/index.json",
|
|
198
|
+
stream=True,
|
|
199
|
+
)
|
|
169
200
|
file_size = int(response.headers.get("content-length", 0))
|
|
170
|
-
print(
|
|
201
|
+
print(
|
|
202
|
+
f"Downloading ~{file_size / 1000000000}Gb {region} AWS billing file to: {filename}"
|
|
203
|
+
)
|
|
171
204
|
|
|
172
|
-
with manager.counter(
|
|
205
|
+
with manager.counter(
|
|
206
|
+
total=file_size, desc=os.path.basename(filename), unit="bytes", leave=False
|
|
207
|
+
) as progress_bar:
|
|
173
208
|
with open(filename, "wb") as file:
|
|
174
209
|
for data in response.iter_content(1048576):
|
|
175
210
|
progress_bar.update(len(data))
|
|
176
211
|
file.write(data)
|
|
177
212
|
|
|
178
213
|
|
|
179
|
-
def reduce_region_json_size(filename:str) ->
|
|
214
|
+
def reduce_region_json_size(filename: str) -> list[dict[str, Any]]:
|
|
180
215
|
"""
|
|
181
216
|
Deletes information in the json file that we don't need, and rewrites it. This makes the file smaller.
|
|
182
217
|
|
|
@@ -185,23 +220,31 @@ def reduce_region_json_size(filename:str) -> List[Dict[str, Any]]:
|
|
|
185
220
|
(with AWS's new Query API), but even those may eventually one day grow ridiculously large, so we do what we can to
|
|
186
221
|
keep the file sizes down (and thus also the amount loaded into memory) to keep this script working for longer.
|
|
187
222
|
"""
|
|
188
|
-
with open(filename
|
|
189
|
-
aws_products = json.loads(f.read())[
|
|
223
|
+
with open(filename) as f:
|
|
224
|
+
aws_products = json.loads(f.read())["products"]
|
|
190
225
|
aws_product_list = list()
|
|
191
226
|
for k in list(aws_products.keys()):
|
|
192
|
-
ec2_attributes = aws_products[k][
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
ec2_attributes = aws_products[k]["attributes"]
|
|
228
|
+
if (
|
|
229
|
+
ec2_attributes.get("tenancy") == "Shared"
|
|
230
|
+
and ec2_attributes.get("operatingSystem") == "Linux"
|
|
231
|
+
and ec2_attributes.get("operation") == "RunInstances"
|
|
232
|
+
and ec2_attributes.get("usagetype").endswith(
|
|
233
|
+
"BoxUsage:" + ec2_attributes["instanceType"]
|
|
234
|
+
)
|
|
235
|
+
):
|
|
236
|
+
aws_product_list.append(
|
|
237
|
+
dict(
|
|
238
|
+
disk=ec2_attributes["storage"],
|
|
239
|
+
loc=ec2_attributes["location"],
|
|
240
|
+
name=ec2_attributes["instanceType"],
|
|
241
|
+
mem=ec2_attributes["memory"],
|
|
242
|
+
cpu=ec2_attributes["vcpu"],
|
|
243
|
+
)
|
|
244
|
+
)
|
|
202
245
|
del aws_products[k]
|
|
203
246
|
del aws_products
|
|
204
|
-
with open(filename,
|
|
247
|
+
with open(filename, "w") as f:
|
|
205
248
|
f.write(json.dumps(dict(aws=aws_product_list), indent=2))
|
|
206
249
|
return aws_product_list
|
|
207
250
|
|
|
@@ -214,14 +257,20 @@ def updateStaticEC2Instances() -> None:
|
|
|
214
257
|
|
|
215
258
|
:return: Nothing. Writes a new 'generatedEC2Lists.py' file.
|
|
216
259
|
"""
|
|
217
|
-
print(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
260
|
+
print(
|
|
261
|
+
"Updating Toil's EC2 lists to the most current version from AWS's bulk API.\n"
|
|
262
|
+
"This may take a while, depending on your internet connection.\n"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
original_aws_instance_list = os.path.join(
|
|
266
|
+
dirname, "generatedEC2Lists.py"
|
|
267
|
+
) # original
|
|
221
268
|
if not os.path.exists(original_aws_instance_list):
|
|
222
269
|
raise RuntimeError(f"Path {original_aws_instance_list} does not exist.")
|
|
223
270
|
# use a temporary file until all info is fetched
|
|
224
|
-
updated_aws_instance_list = os.path.join(
|
|
271
|
+
updated_aws_instance_list = os.path.join(
|
|
272
|
+
dirname, "generatedEC2Lists_tmp.py"
|
|
273
|
+
) # temp
|
|
225
274
|
if os.path.exists(updated_aws_instance_list):
|
|
226
275
|
os.remove(updated_aws_instance_list)
|
|
227
276
|
|
|
@@ -229,15 +278,15 @@ def updateStaticEC2Instances() -> None:
|
|
|
229
278
|
os.mkdir(region_json_dirname)
|
|
230
279
|
|
|
231
280
|
currentEC2List = []
|
|
232
|
-
instancesByRegion:
|
|
281
|
+
instancesByRegion: dict[str, list[str]] = {}
|
|
233
282
|
for region in EC2Regions.keys():
|
|
234
|
-
region_json = os.path.join(region_json_dirname, f
|
|
283
|
+
region_json = os.path.join(region_json_dirname, f"{region}.json")
|
|
235
284
|
|
|
236
285
|
if os.path.exists(region_json):
|
|
237
286
|
try:
|
|
238
|
-
with open(region_json
|
|
239
|
-
aws_products = json.loads(f.read())[
|
|
240
|
-
print(f
|
|
287
|
+
with open(region_json) as f:
|
|
288
|
+
aws_products = json.loads(f.read())["aws"]
|
|
289
|
+
print(f"Reusing previously downloaded json @: {region_json}")
|
|
241
290
|
except:
|
|
242
291
|
os.remove(region_json)
|
|
243
292
|
download_region_json(filename=region_json, region=region)
|
|
@@ -251,14 +300,24 @@ def updateStaticEC2Instances() -> None:
|
|
|
251
300
|
disks, disk_capacity = parse_storage(i["disk"])
|
|
252
301
|
# Determines whether the instance type is from an ARM or AMD family
|
|
253
302
|
# ARM instance names include a digit followed by a 'g' before the instance size
|
|
254
|
-
architecture =
|
|
255
|
-
ec2InstanceList.append(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
303
|
+
architecture = "arm64" if re.search(r".*\dg.*\..*", i["name"]) else "amd64"
|
|
304
|
+
ec2InstanceList.append(
|
|
305
|
+
InstanceType(
|
|
306
|
+
name=i["name"],
|
|
307
|
+
cores=i["cpu"],
|
|
308
|
+
memory=parse_memory(i["mem"]),
|
|
309
|
+
disks=disks,
|
|
310
|
+
disk_capacity=disk_capacity,
|
|
311
|
+
architecture=architecture,
|
|
312
|
+
)
|
|
313
|
+
)
|
|
314
|
+
print(
|
|
315
|
+
"Finished for "
|
|
316
|
+
+ str(region)
|
|
317
|
+
+ ". "
|
|
318
|
+
+ str(len(ec2InstanceList))
|
|
319
|
+
+ " added.\n"
|
|
320
|
+
)
|
|
262
321
|
currentEC2Dict = {_.name: _ for _ in ec2InstanceList}
|
|
263
322
|
for instanceName, instanceTypeObj in currentEC2Dict.items():
|
|
264
323
|
if instanceTypeObj not in currentEC2List:
|
|
@@ -266,8 +325,10 @@ def updateStaticEC2Instances() -> None:
|
|
|
266
325
|
instancesByRegion.setdefault(region, []).append(instanceName)
|
|
267
326
|
|
|
268
327
|
# write provenance note, copyright and imports
|
|
269
|
-
with open(updated_aws_instance_list,
|
|
270
|
-
f.write(
|
|
328
|
+
with open(updated_aws_instance_list, "w") as f:
|
|
329
|
+
f.write(
|
|
330
|
+
textwrap.dedent(
|
|
331
|
+
"""
|
|
271
332
|
# !!! AUTOGENERATED FILE !!!
|
|
272
333
|
# Update with: src/toil/utils/toilUpdateEC2Instances.py
|
|
273
334
|
#
|
|
@@ -284,36 +345,41 @@ def updateStaticEC2Instances() -> None:
|
|
|
284
345
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
285
346
|
# See the License for the specific language governing permissions and
|
|
286
347
|
# limitations under the License.
|
|
287
|
-
from toil.lib.ec2nodes import InstanceType\n\n\n
|
|
348
|
+
from toil.lib.ec2nodes import InstanceType\n\n\n"""
|
|
349
|
+
).format(year=datetime.date.today().strftime("%Y"))[1:]
|
|
350
|
+
)
|
|
288
351
|
|
|
289
352
|
# write header of total EC2 instance type list
|
|
290
|
-
genString = f
|
|
353
|
+
genString = f"# {len(currentEC2List)} Instance Types. Generated {datetime.datetime.now()}.\n"
|
|
291
354
|
genString = genString + "E2Instances = {\n"
|
|
292
355
|
sortedCurrentEC2List = sorted(currentEC2List, key=lambda x: x.name)
|
|
293
356
|
|
|
294
357
|
# write the list of all instances types
|
|
295
358
|
for i in sortedCurrentEC2List:
|
|
296
|
-
genString =
|
|
297
|
-
|
|
359
|
+
genString = (
|
|
360
|
+
genString
|
|
361
|
+
+ f" '{i.name}': InstanceType(name='{i.name}', cores={i.cores}, memory={i.memory}, disks={i.disks}, disk_capacity={i.disk_capacity}, architecture='{i.architecture}'),\n"
|
|
362
|
+
)
|
|
363
|
+
genString = genString + "}\n\n"
|
|
298
364
|
|
|
299
|
-
genString = genString +
|
|
365
|
+
genString = genString + "regionDict = {\n"
|
|
300
366
|
for regionName, instanceList in instancesByRegion.items():
|
|
301
367
|
genString = genString + f" '{regionName}': ["
|
|
302
368
|
for instance in sorted(instanceList):
|
|
303
369
|
genString = genString + f"'{instance}', "
|
|
304
|
-
if genString.endswith(
|
|
370
|
+
if genString.endswith(", "):
|
|
305
371
|
genString = genString[:-2]
|
|
306
|
-
genString = genString +
|
|
307
|
-
if genString.endswith(
|
|
308
|
-
genString = genString[
|
|
309
|
-
genString = genString +
|
|
310
|
-
with open(updated_aws_instance_list,
|
|
372
|
+
genString = genString + "],\n"
|
|
373
|
+
if genString.endswith(",\n"):
|
|
374
|
+
genString = genString[: -len(",\n")]
|
|
375
|
+
genString = genString + "}\n"
|
|
376
|
+
with open(updated_aws_instance_list, "a+") as f:
|
|
311
377
|
f.write(genString)
|
|
312
378
|
|
|
313
379
|
# append key for fetching at the end
|
|
314
|
-
regionKey =
|
|
380
|
+
regionKey = "\nec2InstancesByRegion = {region: [E2Instances[i] for i in instances] for region, instances in regionDict.items()}\n"
|
|
315
381
|
|
|
316
|
-
with open(updated_aws_instance_list,
|
|
382
|
+
with open(updated_aws_instance_list, "a+") as f:
|
|
317
383
|
f.write(regionKey)
|
|
318
384
|
|
|
319
385
|
# replace the instance list with a current list
|
|
@@ -321,5 +387,7 @@ def updateStaticEC2Instances() -> None:
|
|
|
321
387
|
|
|
322
388
|
# delete the aws region json file directory
|
|
323
389
|
if os.path.exists(region_json_dirname):
|
|
324
|
-
print(
|
|
390
|
+
print(
|
|
391
|
+
f"Update Successful! Removing AWS Region JSON Files @: {region_json_dirname}"
|
|
392
|
+
)
|
|
325
393
|
shutil.rmtree(region_json_dirname)
|
toil/lib/encryption/_dummy.py
CHANGED
|
@@ -27,6 +27,8 @@ def decrypt(ciphertext: bytes, keyPath: str) -> bytes:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _bail():
|
|
30
|
-
raise NotImplementedError(
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
raise NotImplementedError(
|
|
31
|
+
"Encryption support is not installed. Consider re-installing toil "
|
|
32
|
+
"with the 'encryption' extra along with any other extras you might "
|
|
33
|
+
"want, e.g. 'pip install toil[encryption,...]'."
|
|
34
|
+
)
|
toil/lib/encryption/_nacl.py
CHANGED
|
@@ -40,11 +40,13 @@ def encrypt(message: bytes, keyPath: str) -> bytes:
|
|
|
40
40
|
>>> import os
|
|
41
41
|
>>> os.remove(k)
|
|
42
42
|
"""
|
|
43
|
-
with open(keyPath,
|
|
43
|
+
with open(keyPath, "rb") as f:
|
|
44
44
|
key = f.read()
|
|
45
45
|
if len(key) != SecretBox.KEY_SIZE:
|
|
46
|
-
raise ValueError(
|
|
47
|
-
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"Key is %d bytes, but must be exactly %d bytes"
|
|
48
|
+
% (len(key), SecretBox.KEY_SIZE)
|
|
49
|
+
)
|
|
48
50
|
sb = SecretBox(key)
|
|
49
51
|
# We generate the nonce using secure random bits. For long enough
|
|
50
52
|
# nonce size, the chance of a random nonce collision becomes
|
|
@@ -87,11 +89,13 @@ def decrypt(ciphertext: bytes, keyPath: str) -> bytes:
|
|
|
87
89
|
>>> import os
|
|
88
90
|
>>> os.remove(k)
|
|
89
91
|
"""
|
|
90
|
-
with open(keyPath,
|
|
92
|
+
with open(keyPath, "rb") as f:
|
|
91
93
|
key = f.read()
|
|
92
94
|
if len(key) != SecretBox.KEY_SIZE:
|
|
93
|
-
raise ValueError(
|
|
94
|
-
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"Key is %d bytes, but must be exactly %d bytes"
|
|
97
|
+
% (len(key), SecretBox.KEY_SIZE)
|
|
98
|
+
)
|
|
95
99
|
sb = SecretBox(key)
|
|
96
100
|
# The nonce is kept with the message.
|
|
97
101
|
return sb.decrypt(ciphertext)
|
toil/lib/encryption/conftest.py
CHANGED
toil/lib/exceptions.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# 5.14.2018: copied into Toil from https://github.com/BD2KGenomics/bd2k-python-lib
|
|
16
16
|
|
|
17
17
|
import sys
|
|
18
|
+
from urllib.parse import ParseResult
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
# TODO: isn't this built in to Python 3 now?
|
|
@@ -38,20 +39,21 @@ class panic:
|
|
|
38
39
|
the primary exception will be reraised.
|
|
39
40
|
"""
|
|
40
41
|
|
|
41
|
-
def __init__(
|
|
42
|
-
super().__init__(
|
|
42
|
+
def __init__(self, log=None):
|
|
43
|
+
super().__init__()
|
|
43
44
|
self.log = log
|
|
44
45
|
self.exc_info = None
|
|
45
46
|
|
|
46
|
-
def __enter__(
|
|
47
|
-
self.exc_info = sys.exc_info(
|
|
47
|
+
def __enter__(self):
|
|
48
|
+
self.exc_info = sys.exc_info()
|
|
48
49
|
|
|
49
|
-
def __exit__(
|
|
50
|
-
if self.log is not None and exc_info and exc_info[
|
|
51
|
-
self.log.warning(
|
|
50
|
+
def __exit__(self, *exc_info):
|
|
51
|
+
if self.log is not None and exc_info and exc_info[0]:
|
|
52
|
+
self.log.warning("Exception during panic", exc_info=exc_info)
|
|
52
53
|
exc_type, exc_value, traceback = self.exc_info
|
|
53
54
|
raise_(exc_type, exc_value, traceback)
|
|
54
55
|
|
|
56
|
+
|
|
55
57
|
def raise_(exc_type, exc_value, traceback) -> None:
|
|
56
58
|
if exc_value is not None:
|
|
57
59
|
exc = exc_value
|
|
@@ -60,3 +62,20 @@ def raise_(exc_type, exc_value, traceback) -> None:
|
|
|
60
62
|
if exc.__traceback__ is not traceback:
|
|
61
63
|
raise exc.with_traceback(traceback)
|
|
62
64
|
raise exc
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UnimplementedURLException(RuntimeError):
|
|
68
|
+
def __init__(self, url: ParseResult, operation: str) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Make a new exception to report that a URL scheme is not implemented, or
|
|
71
|
+
that the implementation can't be loaded because its dependencies are
|
|
72
|
+
not installed.
|
|
73
|
+
|
|
74
|
+
:param url: The given URL
|
|
75
|
+
:param operation: Whether we are trying to 'import' or 'export'
|
|
76
|
+
"""
|
|
77
|
+
super().__init__(
|
|
78
|
+
f"No available job store implementation can {operation} the URL "
|
|
79
|
+
f"'{url.geturl()}'. Ensure Toil has been installed "
|
|
80
|
+
f"with the appropriate extras."
|
|
81
|
+
)
|
toil/lib/expando.py
CHANGED
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
# 5.14.2018: copied into Toil from https://github.com/BD2KGenomics/bd2k-python-lib
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
class Expando(dict):
|
|
18
19
|
"""
|
|
19
|
-
Pass
|
|
20
|
+
Pass initial attributes to the constructor:
|
|
20
21
|
|
|
21
22
|
>>> o = Expando(foo=42)
|
|
22
23
|
>>> o.foo
|
|
@@ -100,14 +101,15 @@ class Expando(dict):
|
|
|
100
101
|
True
|
|
101
102
|
"""
|
|
102
103
|
|
|
103
|
-
def __init__(
|
|
104
|
-
super().__init__(
|
|
104
|
+
def __init__(self, *args, **kwargs):
|
|
105
|
+
super().__init__(*args, **kwargs)
|
|
105
106
|
self.__slots__ = None
|
|
106
107
|
self.__dict__ = self
|
|
107
108
|
|
|
108
109
|
def copy(self):
|
|
109
110
|
return type(self)(self)
|
|
110
111
|
|
|
112
|
+
|
|
111
113
|
class MagicExpando(Expando):
|
|
112
114
|
"""
|
|
113
115
|
Use MagicExpando for chained attribute access.
|