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.
- toil/__init__.py +124 -86
- toil/batchSystems/__init__.py +1 -0
- toil/batchSystems/abstractBatchSystem.py +137 -77
- toil/batchSystems/abstractGridEngineBatchSystem.py +211 -101
- toil/batchSystems/awsBatch.py +237 -128
- toil/batchSystems/cleanup_support.py +22 -16
- toil/batchSystems/contained_executor.py +30 -26
- toil/batchSystems/gridengine.py +85 -49
- toil/batchSystems/htcondor.py +164 -87
- toil/batchSystems/kubernetes.py +622 -386
- toil/batchSystems/local_support.py +17 -12
- toil/batchSystems/lsf.py +132 -79
- toil/batchSystems/lsfHelper.py +13 -11
- toil/batchSystems/mesos/__init__.py +41 -29
- toil/batchSystems/mesos/batchSystem.py +288 -149
- toil/batchSystems/mesos/executor.py +77 -49
- toil/batchSystems/mesos/test/__init__.py +31 -23
- toil/batchSystems/options.py +39 -29
- toil/batchSystems/registry.py +53 -19
- toil/batchSystems/singleMachine.py +293 -123
- toil/batchSystems/slurm.py +651 -155
- toil/batchSystems/torque.py +46 -32
- toil/bus.py +141 -73
- toil/common.py +784 -397
- toil/cwl/__init__.py +1 -1
- toil/cwl/cwltoil.py +1137 -534
- toil/cwl/utils.py +17 -22
- toil/deferred.py +62 -41
- toil/exceptions.py +5 -3
- toil/fileStores/__init__.py +5 -5
- toil/fileStores/abstractFileStore.py +88 -57
- toil/fileStores/cachingFileStore.py +711 -247
- toil/fileStores/nonCachingFileStore.py +113 -75
- toil/job.py +1031 -349
- toil/jobStores/abstractJobStore.py +387 -243
- toil/jobStores/aws/jobStore.py +772 -412
- toil/jobStores/aws/utils.py +161 -109
- toil/jobStores/conftest.py +1 -0
- toil/jobStores/fileJobStore.py +289 -151
- toil/jobStores/googleJobStore.py +137 -70
- toil/jobStores/utils.py +36 -15
- toil/leader.py +614 -269
- toil/lib/accelerators.py +115 -18
- toil/lib/aws/__init__.py +55 -28
- 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 +204 -58
- toil/lib/aws/utils.py +290 -213
- toil/lib/bioio.py +13 -5
- toil/lib/compatibility.py +11 -6
- toil/lib/conversions.py +83 -49
- toil/lib/docker.py +131 -103
- toil/lib/dockstore.py +379 -0
- toil/lib/ec2.py +322 -209
- toil/lib/ec2nodes.py +174 -105
- 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 +4 -2
- toil/lib/ftp_utils.py +217 -0
- toil/lib/generatedEC2Lists.py +127 -19
- toil/lib/history.py +1271 -0
- toil/lib/history_submission.py +681 -0
- toil/lib/humanize.py +6 -2
- toil/lib/io.py +121 -12
- toil/lib/iterables.py +4 -2
- toil/lib/memoize.py +12 -8
- toil/lib/misc.py +83 -18
- toil/lib/objects.py +2 -2
- toil/lib/resources.py +19 -7
- toil/lib/retry.py +125 -87
- toil/lib/threading.py +282 -80
- toil/lib/throttle.py +15 -14
- toil/lib/trs.py +390 -0
- toil/lib/web.py +38 -0
- toil/options/common.py +850 -402
- toil/options/cwl.py +185 -90
- toil/options/runner.py +50 -0
- toil/options/wdl.py +70 -19
- toil/provisioners/__init__.py +111 -46
- toil/provisioners/abstractProvisioner.py +322 -157
- toil/provisioners/aws/__init__.py +62 -30
- toil/provisioners/aws/awsProvisioner.py +980 -627
- toil/provisioners/clusterScaler.py +541 -279
- toil/provisioners/gceProvisioner.py +283 -180
- toil/provisioners/node.py +147 -79
- toil/realtimeLogger.py +34 -22
- toil/resource.py +137 -75
- toil/server/app.py +127 -61
- toil/server/celery_app.py +3 -1
- toil/server/cli/wes_cwl_runner.py +84 -55
- toil/server/utils.py +56 -31
- 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 +183 -65
- toil/test/__init__.py +263 -179
- toil/test/batchSystems/batchSystemTest.py +438 -195
- toil/test/batchSystems/batch_system_plugin_test.py +18 -7
- toil/test/batchSystems/test_gridengine.py +173 -0
- toil/test/batchSystems/test_lsf_helper.py +67 -58
- toil/test/batchSystems/test_slurm.py +265 -49
- toil/test/cactus/test_cactus_integration.py +20 -22
- toil/test/cwl/conftest.py +39 -0
- toil/test/cwl/cwlTest.py +375 -72
- toil/test/cwl/measure_default_memory.cwl +12 -0
- toil/test/cwl/not_run_required_input.cwl +29 -0
- toil/test/cwl/optional-file.cwl +18 -0
- toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
- toil/test/docs/scriptsTest.py +60 -34
- toil/test/jobStores/jobStoreTest.py +412 -235
- toil/test/lib/aws/test_iam.py +116 -48
- 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 +57 -49
- toil/test/lib/test_history.py +212 -0
- toil/test/lib/test_misc.py +12 -5
- toil/test/lib/test_trs.py +161 -0
- toil/test/mesos/MesosDataStructuresTest.py +23 -10
- toil/test/mesos/helloWorld.py +7 -6
- toil/test/mesos/stress.py +25 -20
- toil/test/options/options.py +7 -2
- toil/test/provisioners/aws/awsProvisionerTest.py +293 -140
- toil/test/provisioners/clusterScalerTest.py +440 -250
- toil/test/provisioners/clusterTest.py +81 -42
- 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 +140 -100
- 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 +33 -26
- toil/test/src/environmentTest.py +20 -10
- toil/test/src/fileStoreTest.py +538 -271
- toil/test/src/helloWorldTest.py +7 -4
- toil/test/src/importExportFileTest.py +61 -31
- toil/test/src/jobDescriptionTest.py +32 -17
- 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 +120 -70
- 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 +6 -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 +33 -16
- toil/test/utils/toilDebugTest.py +70 -58
- toil/test/utils/toilKillTest.py +4 -5
- toil/test/utils/utilsTest.py +239 -102
- toil/test/wdl/wdltoil_test.py +789 -148
- toil/test/wdl/wdltoil_test_kubernetes.py +37 -23
- toil/toilState.py +52 -26
- toil/utils/toilConfig.py +13 -4
- toil/utils/toilDebugFile.py +44 -27
- toil/utils/toilDebugJob.py +85 -25
- toil/utils/toilDestroyCluster.py +11 -6
- toil/utils/toilKill.py +8 -3
- toil/utils/toilLaunchCluster.py +251 -145
- toil/utils/toilMain.py +37 -16
- toil/utils/toilRsyncCluster.py +27 -14
- toil/utils/toilSshCluster.py +45 -22
- toil/utils/toilStats.py +75 -36
- toil/utils/toilStatus.py +226 -119
- toil/utils/toilUpdateEC2Instances.py +3 -1
- toil/version.py +6 -6
- toil/wdl/utils.py +5 -5
- toil/wdl/wdltoil.py +3528 -1053
- toil/worker.py +370 -149
- toil-8.1.0b1.dist-info/METADATA +178 -0
- toil-8.1.0b1.dist-info/RECORD +259 -0
- {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/WHEEL +1 -1
- toil-7.0.0.dist-info/METADATA +0 -158
- toil-7.0.0.dist-info/RECORD +0 -244
- {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/LICENSE +0 -0
- {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/entry_points.txt +0 -0
- {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/top_level.txt +0 -0
toil/lib/ec2nodes.py
CHANGED
|
@@ -16,74 +16,91 @@ 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
|
|
20
|
+
import textwrap
|
|
21
|
+
from typing import Any, Union
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
import enlighten # type: ignore
|
|
25
24
|
|
|
25
|
+
from toil.lib.web import web_session
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
manager = enlighten.get_manager()
|
|
29
29
|
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
|
-
|
|
30
|
+
region_json_dirname = os.path.join(dirname, "region_jsons")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
EC2Regions = {
|
|
34
|
+
"us-west-1": "US West (N. California)",
|
|
35
|
+
"us-west-2": "US West (Oregon)",
|
|
36
|
+
"us-east-1": "US East (N. Virginia)",
|
|
37
|
+
"us-east-2": "US East (Ohio)",
|
|
38
|
+
"us-gov-west-1": "AWS GovCloud (US)",
|
|
39
|
+
"ca-central-1": "Canada (Central)",
|
|
40
|
+
"ap-northeast-1": "Asia Pacific (Tokyo)",
|
|
41
|
+
"ap-northeast-2": "Asia Pacific (Seoul)",
|
|
42
|
+
"ap-northeast-3": "Asia Pacific (Osaka-Local)",
|
|
43
|
+
"ap-southeast-1": "Asia Pacific (Singapore)",
|
|
44
|
+
"ap-southeast-2": "Asia Pacific (Sydney)",
|
|
45
|
+
"ap-south-1": "Asia Pacific (Mumbai)",
|
|
46
|
+
"eu-west-1": "EU (Ireland)",
|
|
47
|
+
"eu-west-2": "EU (London)",
|
|
48
|
+
"eu-west-3": "EU (Paris)",
|
|
49
|
+
"eu-central-1": "EU (Frankfurt)",
|
|
50
|
+
"sa-east-1": "South America (Sao Paulo)",
|
|
51
|
+
}
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
class InstanceType:
|
|
53
|
-
__slots__ = (
|
|
54
|
-
|
|
55
|
-
def __init__(
|
|
55
|
+
__slots__ = ("name", "cores", "memory", "disks", "disk_capacity", "architecture")
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
name: str,
|
|
60
|
+
cores: int,
|
|
61
|
+
memory: float,
|
|
62
|
+
disks: float,
|
|
63
|
+
disk_capacity: float,
|
|
64
|
+
architecture: str,
|
|
65
|
+
):
|
|
56
66
|
self.name = name # the API name of the instance type
|
|
57
67
|
self.cores = cores # the number of cores
|
|
58
68
|
self.memory = memory # RAM in GiB
|
|
59
69
|
self.disks = disks # the number of ephemeral (aka 'instance store') volumes
|
|
60
|
-
self.disk_capacity =
|
|
61
|
-
|
|
70
|
+
self.disk_capacity = (
|
|
71
|
+
disk_capacity # the capacity of each ephemeral volume in GiB
|
|
72
|
+
)
|
|
73
|
+
self.architecture = architecture # the architecture of the instance type. Can be either amd64 or arm64
|
|
62
74
|
|
|
63
75
|
def __str__(self) -> str:
|
|
64
|
-
return (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
return (
|
|
77
|
+
"Type: {}\n"
|
|
78
|
+
"Cores: {}\n"
|
|
79
|
+
"Disks: {}\n"
|
|
80
|
+
"Memory: {}\n"
|
|
81
|
+
"Disk Capacity: {}\n"
|
|
82
|
+
"Architecture: {}\n"
|
|
83
|
+
"".format(
|
|
71
84
|
self.name,
|
|
72
85
|
self.cores,
|
|
73
86
|
self.disks,
|
|
74
87
|
self.memory,
|
|
75
88
|
self.disk_capacity,
|
|
76
|
-
self.architecture
|
|
89
|
+
self.architecture,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
77
92
|
|
|
78
93
|
def __eq__(self, other: object) -> bool:
|
|
79
94
|
if not isinstance(other, InstanceType):
|
|
80
95
|
return NotImplemented
|
|
81
|
-
if (
|
|
82
|
-
self.
|
|
83
|
-
self.
|
|
84
|
-
self.
|
|
85
|
-
self.
|
|
86
|
-
self.
|
|
96
|
+
if (
|
|
97
|
+
self.name == other.name
|
|
98
|
+
and self.cores == other.cores
|
|
99
|
+
and self.memory == other.memory
|
|
100
|
+
and self.disks == other.disks
|
|
101
|
+
and self.disk_capacity == other.disk_capacity
|
|
102
|
+
and self.architecture == other.architecture
|
|
103
|
+
):
|
|
87
104
|
return True
|
|
88
105
|
return False
|
|
89
106
|
|
|
@@ -95,7 +112,7 @@ def is_number(s: str) -> bool:
|
|
|
95
112
|
:param s: Any unicode string.
|
|
96
113
|
:return: True if s represents a number, False otherwise.
|
|
97
114
|
"""
|
|
98
|
-
s = s.replace(
|
|
115
|
+
s = s.replace(",", "")
|
|
99
116
|
try:
|
|
100
117
|
float(s)
|
|
101
118
|
return True
|
|
@@ -103,6 +120,7 @@ def is_number(s: str) -> bool:
|
|
|
103
120
|
pass
|
|
104
121
|
try:
|
|
105
122
|
import unicodedata
|
|
123
|
+
|
|
106
124
|
unicodedata.numeric(s)
|
|
107
125
|
return True
|
|
108
126
|
except (TypeError, ValueError) as e:
|
|
@@ -110,7 +128,9 @@ def is_number(s: str) -> bool:
|
|
|
110
128
|
return False
|
|
111
129
|
|
|
112
130
|
|
|
113
|
-
def parse_storage(
|
|
131
|
+
def parse_storage(
|
|
132
|
+
storage_info: str,
|
|
133
|
+
) -> Union[list[int], tuple[Union[int, float], float]]:
|
|
114
134
|
"""
|
|
115
135
|
Parses EC2 JSON storage param string into a number.
|
|
116
136
|
|
|
@@ -129,12 +149,21 @@ def parse_storage(storage_info: str) -> Union[List[int], Tuple[Union[int, float]
|
|
|
129
149
|
return [0, 0]
|
|
130
150
|
else:
|
|
131
151
|
specs = storage_info.strip().split()
|
|
132
|
-
if is_number(specs[0]) and specs[1] ==
|
|
133
|
-
return float(specs[0].replace(
|
|
134
|
-
elif
|
|
135
|
-
|
|
152
|
+
if is_number(specs[0]) and specs[1].lower() == "x" and is_number(specs[2]):
|
|
153
|
+
return float(specs[0].replace(",", "")), float(specs[2].replace(",", ""))
|
|
154
|
+
elif (
|
|
155
|
+
is_number(specs[0])
|
|
156
|
+
and specs[1] == "GB"
|
|
157
|
+
and specs[2] == "NVMe"
|
|
158
|
+
and specs[3] == "SSD"
|
|
159
|
+
):
|
|
160
|
+
return 1, float(specs[0].replace(",", ""))
|
|
161
|
+
elif is_number(specs[0]) and specs[1].lower() == "x" and is_number(specs[2][:-2]) and specs[2][-2:] == "GB":
|
|
162
|
+
return float(specs[0].replace(",", "")), float(specs[2][:-2].replace(",", ""))
|
|
136
163
|
else:
|
|
137
|
-
raise RuntimeError(
|
|
164
|
+
raise RuntimeError(
|
|
165
|
+
f"EC2 JSON format has likely changed. Error parsing disk specs : {storage_info.strip()}"
|
|
166
|
+
)
|
|
138
167
|
|
|
139
168
|
|
|
140
169
|
def parse_memory(mem_info: str) -> float:
|
|
@@ -148,14 +177,14 @@ def parse_memory(mem_info: str) -> float:
|
|
|
148
177
|
:param mem_info: EC2 JSON memory param string.
|
|
149
178
|
:return: A float representing memory in GiB.
|
|
150
179
|
"""
|
|
151
|
-
mem = mem_info.replace(
|
|
152
|
-
if mem[1] ==
|
|
180
|
+
mem = mem_info.replace(",", "").split()
|
|
181
|
+
if mem[1] == "GiB":
|
|
153
182
|
return float(mem[0])
|
|
154
183
|
else:
|
|
155
|
-
raise RuntimeError(
|
|
184
|
+
raise RuntimeError("EC2 JSON format has likely changed. Error parsing memory.")
|
|
156
185
|
|
|
157
186
|
|
|
158
|
-
def download_region_json(filename: str, region: str =
|
|
187
|
+
def download_region_json(filename: str, region: str = "us-east-1") -> None:
|
|
159
188
|
"""
|
|
160
189
|
Downloads and writes the AWS Billing JSON to a file using the AWS pricing API.
|
|
161
190
|
|
|
@@ -165,18 +194,25 @@ def download_region_json(filename: str, region: str = 'us-east-1') -> None:
|
|
|
165
194
|
aws instance name (example: 't2.micro'), and the value is an
|
|
166
195
|
InstanceType object representing that aws instance name.
|
|
167
196
|
"""
|
|
168
|
-
response =
|
|
197
|
+
response = web_session.get(
|
|
198
|
+
f"https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/{region}/index.json",
|
|
199
|
+
stream=True,
|
|
200
|
+
)
|
|
169
201
|
file_size = int(response.headers.get("content-length", 0))
|
|
170
|
-
print(
|
|
202
|
+
print(
|
|
203
|
+
f"Downloading ~{file_size / 1000000000}Gb {region} AWS billing file to: {filename}"
|
|
204
|
+
)
|
|
171
205
|
|
|
172
|
-
with manager.counter(
|
|
206
|
+
with manager.counter(
|
|
207
|
+
total=file_size, desc=os.path.basename(filename), unit="bytes", leave=False
|
|
208
|
+
) as progress_bar:
|
|
173
209
|
with open(filename, "wb") as file:
|
|
174
210
|
for data in response.iter_content(1048576):
|
|
175
211
|
progress_bar.update(len(data))
|
|
176
212
|
file.write(data)
|
|
177
213
|
|
|
178
214
|
|
|
179
|
-
def reduce_region_json_size(filename:str) ->
|
|
215
|
+
def reduce_region_json_size(filename: str) -> list[dict[str, Any]]:
|
|
180
216
|
"""
|
|
181
217
|
Deletes information in the json file that we don't need, and rewrites it. This makes the file smaller.
|
|
182
218
|
|
|
@@ -185,23 +221,31 @@ def reduce_region_json_size(filename:str) -> List[Dict[str, Any]]:
|
|
|
185
221
|
(with AWS's new Query API), but even those may eventually one day grow ridiculously large, so we do what we can to
|
|
186
222
|
keep the file sizes down (and thus also the amount loaded into memory) to keep this script working for longer.
|
|
187
223
|
"""
|
|
188
|
-
with open(filename
|
|
189
|
-
aws_products = json.loads(f.read())[
|
|
224
|
+
with open(filename) as f:
|
|
225
|
+
aws_products = json.loads(f.read())["products"]
|
|
190
226
|
aws_product_list = list()
|
|
191
227
|
for k in list(aws_products.keys()):
|
|
192
|
-
ec2_attributes = aws_products[k][
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
228
|
+
ec2_attributes = aws_products[k]["attributes"]
|
|
229
|
+
if (
|
|
230
|
+
ec2_attributes.get("tenancy") == "Shared"
|
|
231
|
+
and ec2_attributes.get("operatingSystem") == "Linux"
|
|
232
|
+
and ec2_attributes.get("operation") == "RunInstances"
|
|
233
|
+
and ec2_attributes.get("usagetype").endswith(
|
|
234
|
+
"BoxUsage:" + ec2_attributes["instanceType"]
|
|
235
|
+
)
|
|
236
|
+
):
|
|
237
|
+
aws_product_list.append(
|
|
238
|
+
dict(
|
|
239
|
+
disk=ec2_attributes["storage"],
|
|
240
|
+
loc=ec2_attributes["location"],
|
|
241
|
+
name=ec2_attributes["instanceType"],
|
|
242
|
+
mem=ec2_attributes["memory"],
|
|
243
|
+
cpu=ec2_attributes["vcpu"],
|
|
244
|
+
)
|
|
245
|
+
)
|
|
202
246
|
del aws_products[k]
|
|
203
247
|
del aws_products
|
|
204
|
-
with open(filename,
|
|
248
|
+
with open(filename, "w") as f:
|
|
205
249
|
f.write(json.dumps(dict(aws=aws_product_list), indent=2))
|
|
206
250
|
return aws_product_list
|
|
207
251
|
|
|
@@ -214,14 +258,20 @@ def updateStaticEC2Instances() -> None:
|
|
|
214
258
|
|
|
215
259
|
:return: Nothing. Writes a new 'generatedEC2Lists.py' file.
|
|
216
260
|
"""
|
|
217
|
-
print(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
261
|
+
print(
|
|
262
|
+
"Updating Toil's EC2 lists to the most current version from AWS's bulk API.\n"
|
|
263
|
+
"This may take a while, depending on your internet connection.\n"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
original_aws_instance_list = os.path.join(
|
|
267
|
+
dirname, "generatedEC2Lists.py"
|
|
268
|
+
) # original
|
|
221
269
|
if not os.path.exists(original_aws_instance_list):
|
|
222
270
|
raise RuntimeError(f"Path {original_aws_instance_list} does not exist.")
|
|
223
271
|
# use a temporary file until all info is fetched
|
|
224
|
-
updated_aws_instance_list = os.path.join(
|
|
272
|
+
updated_aws_instance_list = os.path.join(
|
|
273
|
+
dirname, "generatedEC2Lists_tmp.py"
|
|
274
|
+
) # temp
|
|
225
275
|
if os.path.exists(updated_aws_instance_list):
|
|
226
276
|
os.remove(updated_aws_instance_list)
|
|
227
277
|
|
|
@@ -229,15 +279,15 @@ def updateStaticEC2Instances() -> None:
|
|
|
229
279
|
os.mkdir(region_json_dirname)
|
|
230
280
|
|
|
231
281
|
currentEC2List = []
|
|
232
|
-
instancesByRegion:
|
|
282
|
+
instancesByRegion: dict[str, list[str]] = {}
|
|
233
283
|
for region in EC2Regions.keys():
|
|
234
|
-
region_json = os.path.join(region_json_dirname, f
|
|
284
|
+
region_json = os.path.join(region_json_dirname, f"{region}.json")
|
|
235
285
|
|
|
236
286
|
if os.path.exists(region_json):
|
|
237
287
|
try:
|
|
238
|
-
with open(region_json
|
|
239
|
-
aws_products = json.loads(f.read())[
|
|
240
|
-
print(f
|
|
288
|
+
with open(region_json) as f:
|
|
289
|
+
aws_products = json.loads(f.read())["aws"]
|
|
290
|
+
print(f"Reusing previously downloaded json @: {region_json}")
|
|
241
291
|
except:
|
|
242
292
|
os.remove(region_json)
|
|
243
293
|
download_region_json(filename=region_json, region=region)
|
|
@@ -251,14 +301,24 @@ def updateStaticEC2Instances() -> None:
|
|
|
251
301
|
disks, disk_capacity = parse_storage(i["disk"])
|
|
252
302
|
# Determines whether the instance type is from an ARM or AMD family
|
|
253
303
|
# 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
|
-
|
|
304
|
+
architecture = "arm64" if re.search(r".*\dg.*\..*", i["name"]) else "amd64"
|
|
305
|
+
ec2InstanceList.append(
|
|
306
|
+
InstanceType(
|
|
307
|
+
name=i["name"],
|
|
308
|
+
cores=i["cpu"],
|
|
309
|
+
memory=parse_memory(i["mem"]),
|
|
310
|
+
disks=disks,
|
|
311
|
+
disk_capacity=disk_capacity,
|
|
312
|
+
architecture=architecture,
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
print(
|
|
316
|
+
"Finished for "
|
|
317
|
+
+ str(region)
|
|
318
|
+
+ ". "
|
|
319
|
+
+ str(len(ec2InstanceList))
|
|
320
|
+
+ " added.\n"
|
|
321
|
+
)
|
|
262
322
|
currentEC2Dict = {_.name: _ for _ in ec2InstanceList}
|
|
263
323
|
for instanceName, instanceTypeObj in currentEC2Dict.items():
|
|
264
324
|
if instanceTypeObj not in currentEC2List:
|
|
@@ -266,8 +326,10 @@ def updateStaticEC2Instances() -> None:
|
|
|
266
326
|
instancesByRegion.setdefault(region, []).append(instanceName)
|
|
267
327
|
|
|
268
328
|
# write provenance note, copyright and imports
|
|
269
|
-
with open(updated_aws_instance_list,
|
|
270
|
-
f.write(
|
|
329
|
+
with open(updated_aws_instance_list, "w") as f:
|
|
330
|
+
f.write(
|
|
331
|
+
textwrap.dedent(
|
|
332
|
+
"""
|
|
271
333
|
# !!! AUTOGENERATED FILE !!!
|
|
272
334
|
# Update with: src/toil/utils/toilUpdateEC2Instances.py
|
|
273
335
|
#
|
|
@@ -284,36 +346,41 @@ def updateStaticEC2Instances() -> None:
|
|
|
284
346
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
285
347
|
# See the License for the specific language governing permissions and
|
|
286
348
|
# limitations under the License.
|
|
287
|
-
from toil.lib.ec2nodes import InstanceType\n\n\n
|
|
349
|
+
from toil.lib.ec2nodes import InstanceType\n\n\n"""
|
|
350
|
+
).format(year=datetime.date.today().strftime("%Y"))[1:]
|
|
351
|
+
)
|
|
288
352
|
|
|
289
353
|
# write header of total EC2 instance type list
|
|
290
|
-
genString = f
|
|
354
|
+
genString = f"# {len(currentEC2List)} Instance Types. Generated {datetime.datetime.now()}.\n"
|
|
291
355
|
genString = genString + "E2Instances = {\n"
|
|
292
356
|
sortedCurrentEC2List = sorted(currentEC2List, key=lambda x: x.name)
|
|
293
357
|
|
|
294
358
|
# write the list of all instances types
|
|
295
359
|
for i in sortedCurrentEC2List:
|
|
296
|
-
genString =
|
|
297
|
-
|
|
360
|
+
genString = (
|
|
361
|
+
genString
|
|
362
|
+
+ 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"
|
|
363
|
+
)
|
|
364
|
+
genString = genString + "}\n\n"
|
|
298
365
|
|
|
299
|
-
genString = genString +
|
|
366
|
+
genString = genString + "regionDict = {\n"
|
|
300
367
|
for regionName, instanceList in instancesByRegion.items():
|
|
301
368
|
genString = genString + f" '{regionName}': ["
|
|
302
369
|
for instance in sorted(instanceList):
|
|
303
370
|
genString = genString + f"'{instance}', "
|
|
304
|
-
if genString.endswith(
|
|
371
|
+
if genString.endswith(", "):
|
|
305
372
|
genString = genString[:-2]
|
|
306
|
-
genString = genString +
|
|
307
|
-
if genString.endswith(
|
|
308
|
-
genString = genString[
|
|
309
|
-
genString = genString +
|
|
310
|
-
with open(updated_aws_instance_list,
|
|
373
|
+
genString = genString + "],\n"
|
|
374
|
+
if genString.endswith(",\n"):
|
|
375
|
+
genString = genString[: -len(",\n")]
|
|
376
|
+
genString = genString + "}\n"
|
|
377
|
+
with open(updated_aws_instance_list, "a+") as f:
|
|
311
378
|
f.write(genString)
|
|
312
379
|
|
|
313
380
|
# append key for fetching at the end
|
|
314
|
-
regionKey =
|
|
381
|
+
regionKey = "\nec2InstancesByRegion = {region: [E2Instances[i] for i in instances] for region, instances in regionDict.items()}\n"
|
|
315
382
|
|
|
316
|
-
with open(updated_aws_instance_list,
|
|
383
|
+
with open(updated_aws_instance_list, "a+") as f:
|
|
317
384
|
f.write(regionKey)
|
|
318
385
|
|
|
319
386
|
# replace the instance list with a current list
|
|
@@ -321,5 +388,7 @@ def updateStaticEC2Instances() -> None:
|
|
|
321
388
|
|
|
322
389
|
# delete the aws region json file directory
|
|
323
390
|
if os.path.exists(region_json_dirname):
|
|
324
|
-
print(
|
|
391
|
+
print(
|
|
392
|
+
f"Update Successful! Removing AWS Region JSON Files @: {region_json_dirname}"
|
|
393
|
+
)
|
|
325
394
|
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,6 +14,7 @@
|
|
|
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
20
|
Pass initial attributes to the constructor:
|
|
@@ -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.
|