toil 5.12.0__py3-none-any.whl → 6.1.0a1__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 +18 -13
- toil/batchSystems/abstractBatchSystem.py +21 -10
- toil/batchSystems/abstractGridEngineBatchSystem.py +2 -2
- toil/batchSystems/awsBatch.py +14 -14
- toil/batchSystems/contained_executor.py +3 -3
- toil/batchSystems/htcondor.py +0 -1
- toil/batchSystems/kubernetes.py +34 -31
- toil/batchSystems/local_support.py +3 -1
- toil/batchSystems/mesos/batchSystem.py +7 -7
- toil/batchSystems/options.py +32 -83
- toil/batchSystems/registry.py +104 -23
- toil/batchSystems/singleMachine.py +16 -13
- toil/batchSystems/slurm.py +3 -3
- toil/batchSystems/torque.py +0 -1
- toil/bus.py +6 -8
- toil/common.py +532 -743
- toil/cwl/__init__.py +28 -32
- toil/cwl/cwltoil.py +523 -520
- toil/cwl/utils.py +55 -10
- toil/fileStores/__init__.py +2 -2
- toil/fileStores/abstractFileStore.py +36 -11
- toil/fileStores/cachingFileStore.py +607 -530
- toil/fileStores/nonCachingFileStore.py +43 -10
- toil/job.py +140 -75
- toil/jobStores/abstractJobStore.py +147 -79
- toil/jobStores/aws/jobStore.py +23 -9
- toil/jobStores/aws/utils.py +1 -2
- toil/jobStores/fileJobStore.py +117 -19
- toil/jobStores/googleJobStore.py +16 -7
- toil/jobStores/utils.py +5 -6
- toil/leader.py +71 -43
- toil/lib/accelerators.py +10 -5
- toil/lib/aws/__init__.py +3 -14
- toil/lib/aws/ami.py +22 -9
- toil/lib/aws/iam.py +21 -13
- toil/lib/aws/session.py +2 -16
- toil/lib/aws/utils.py +4 -5
- toil/lib/compatibility.py +1 -1
- toil/lib/conversions.py +7 -3
- toil/lib/docker.py +22 -23
- toil/lib/ec2.py +10 -6
- toil/lib/ec2nodes.py +106 -100
- toil/lib/encryption/_nacl.py +2 -1
- toil/lib/generatedEC2Lists.py +325 -18
- toil/lib/io.py +21 -0
- toil/lib/misc.py +1 -1
- toil/lib/resources.py +1 -1
- toil/lib/threading.py +74 -26
- toil/options/common.py +738 -0
- toil/options/cwl.py +336 -0
- toil/options/wdl.py +32 -0
- toil/provisioners/abstractProvisioner.py +1 -4
- toil/provisioners/aws/__init__.py +3 -6
- toil/provisioners/aws/awsProvisioner.py +6 -0
- toil/provisioners/clusterScaler.py +3 -2
- toil/provisioners/gceProvisioner.py +2 -2
- toil/realtimeLogger.py +2 -1
- toil/resource.py +24 -18
- toil/server/app.py +2 -3
- toil/server/cli/wes_cwl_runner.py +4 -4
- toil/server/utils.py +1 -1
- toil/server/wes/abstract_backend.py +3 -2
- toil/server/wes/amazon_wes_utils.py +5 -4
- toil/server/wes/tasks.py +2 -3
- toil/server/wes/toil_backend.py +2 -10
- toil/server/wsgi_app.py +2 -0
- toil/serviceManager.py +12 -10
- toil/statsAndLogging.py +5 -1
- toil/test/__init__.py +29 -54
- toil/test/batchSystems/batchSystemTest.py +11 -111
- toil/test/batchSystems/test_slurm.py +3 -2
- toil/test/cwl/cwlTest.py +213 -90
- toil/test/cwl/glob_dir.cwl +15 -0
- toil/test/cwl/preemptible.cwl +21 -0
- toil/test/cwl/preemptible_expression.cwl +28 -0
- toil/test/cwl/revsort.cwl +1 -1
- toil/test/cwl/revsort2.cwl +1 -1
- toil/test/docs/scriptsTest.py +0 -1
- toil/test/jobStores/jobStoreTest.py +27 -16
- toil/test/lib/aws/test_iam.py +4 -14
- toil/test/lib/aws/test_utils.py +0 -3
- toil/test/lib/dockerTest.py +4 -4
- toil/test/lib/test_ec2.py +11 -16
- toil/test/mesos/helloWorld.py +4 -5
- toil/test/mesos/stress.py +1 -1
- toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
- toil/test/provisioners/clusterScalerTest.py +6 -4
- toil/test/provisioners/clusterTest.py +14 -3
- toil/test/provisioners/gceProvisionerTest.py +0 -6
- toil/test/provisioners/restartScript.py +3 -2
- toil/test/server/serverTest.py +1 -1
- toil/test/sort/restart_sort.py +2 -1
- toil/test/sort/sort.py +2 -1
- toil/test/sort/sortTest.py +2 -13
- toil/test/src/autoDeploymentTest.py +45 -45
- toil/test/src/busTest.py +5 -5
- toil/test/src/checkpointTest.py +2 -2
- toil/test/src/deferredFunctionTest.py +1 -1
- toil/test/src/fileStoreTest.py +32 -16
- toil/test/src/helloWorldTest.py +1 -1
- toil/test/src/importExportFileTest.py +1 -1
- toil/test/src/jobDescriptionTest.py +2 -1
- toil/test/src/jobServiceTest.py +1 -1
- toil/test/src/jobTest.py +18 -18
- toil/test/src/miscTests.py +5 -3
- toil/test/src/promisedRequirementTest.py +3 -3
- toil/test/src/realtimeLoggerTest.py +1 -1
- toil/test/src/resourceTest.py +2 -2
- toil/test/src/restartDAGTest.py +1 -1
- toil/test/src/resumabilityTest.py +36 -2
- toil/test/src/retainTempDirTest.py +1 -1
- toil/test/src/systemTest.py +2 -2
- toil/test/src/toilContextManagerTest.py +2 -2
- toil/test/src/userDefinedJobArgTypeTest.py +1 -1
- toil/test/utils/toilDebugTest.py +98 -32
- toil/test/utils/toilKillTest.py +2 -2
- toil/test/utils/utilsTest.py +20 -0
- toil/test/wdl/wdltoil_test.py +148 -45
- toil/toilState.py +7 -6
- toil/utils/toilClean.py +1 -1
- toil/utils/toilConfig.py +36 -0
- toil/utils/toilDebugFile.py +60 -33
- toil/utils/toilDebugJob.py +39 -12
- toil/utils/toilDestroyCluster.py +1 -1
- toil/utils/toilKill.py +1 -1
- toil/utils/toilLaunchCluster.py +13 -2
- toil/utils/toilMain.py +3 -2
- toil/utils/toilRsyncCluster.py +1 -1
- toil/utils/toilSshCluster.py +1 -1
- toil/utils/toilStats.py +240 -143
- toil/utils/toilStatus.py +1 -4
- toil/version.py +11 -11
- toil/wdl/utils.py +2 -122
- toil/wdl/wdltoil.py +999 -386
- toil/worker.py +25 -31
- {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/METADATA +60 -53
- toil-6.1.0a1.dist-info/RECORD +237 -0
- {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/WHEEL +1 -1
- {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/entry_points.txt +0 -1
- toil/batchSystems/parasol.py +0 -379
- toil/batchSystems/tes.py +0 -459
- toil/test/batchSystems/parasolTestSupport.py +0 -117
- toil/test/wdl/builtinTest.py +0 -506
- toil/test/wdl/conftest.py +0 -23
- toil/test/wdl/toilwdlTest.py +0 -522
- toil/wdl/toilwdl.py +0 -141
- toil/wdl/versions/dev.py +0 -107
- toil/wdl/versions/draft2.py +0 -980
- toil/wdl/versions/v1.py +0 -794
- toil/wdl/wdl_analysis.py +0 -116
- toil/wdl/wdl_functions.py +0 -997
- toil/wdl/wdl_synthesis.py +0 -1011
- toil/wdl/wdl_types.py +0 -243
- toil-5.12.0.dist-info/RECORD +0 -244
- /toil/{wdl/versions → options}/__init__.py +0 -0
- {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/LICENSE +0 -0
- {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/top_level.txt +0 -0
toil/lib/accelerators.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
18
|
import subprocess
|
|
19
|
-
from typing import Dict, List,
|
|
19
|
+
from typing import Dict, List, Set, Union, cast
|
|
20
20
|
from xml.dom import minidom
|
|
21
21
|
|
|
22
22
|
from toil.job import AcceleratorRequirement
|
|
@@ -92,10 +92,15 @@ def count_nvidia_gpus() -> int:
|
|
|
92
92
|
# <https://github.com/common-workflow-language/cwltool/blob/6f29c59fb1b5426ef6f2891605e8fa2d08f1a8da/cwltool/cuda.py>
|
|
93
93
|
# Some example output is here: <https://gist.github.com/loretoparisi/2620b777562c2dfd50d6b618b5f20867>
|
|
94
94
|
try:
|
|
95
|
-
return int(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
return int(
|
|
96
|
+
cast(
|
|
97
|
+
minidom.Text,
|
|
98
|
+
minidom.parseString(subprocess.check_output(["nvidia-smi", "-q", "-x"]))
|
|
99
|
+
.getElementsByTagName("attached_gpus")[0]
|
|
100
|
+
.firstChild,
|
|
101
|
+
).data
|
|
102
|
+
)
|
|
103
|
+
except:
|
|
99
104
|
return 0
|
|
100
105
|
|
|
101
106
|
# TODO: Parse each gpu > product_name > text content and convert to some
|
toil/lib/aws/__init__.py
CHANGED
|
@@ -11,27 +11,15 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
import collections
|
|
15
|
-
import inspect
|
|
16
14
|
import json
|
|
17
15
|
import logging
|
|
18
16
|
import os
|
|
19
17
|
import re
|
|
20
18
|
import socket
|
|
21
|
-
import
|
|
22
|
-
from
|
|
23
|
-
from typing import (Any,
|
|
24
|
-
Callable,
|
|
25
|
-
Dict,
|
|
26
|
-
Iterable,
|
|
27
|
-
List,
|
|
28
|
-
MutableMapping,
|
|
29
|
-
Optional,
|
|
30
|
-
TypeVar,
|
|
31
|
-
Union)
|
|
19
|
+
from http.client import HTTPException
|
|
20
|
+
from typing import Dict, MutableMapping, Optional
|
|
32
21
|
from urllib.error import URLError
|
|
33
22
|
from urllib.request import urlopen
|
|
34
|
-
from http.client import HTTPException
|
|
35
23
|
|
|
36
24
|
logger = logging.getLogger(__name__)
|
|
37
25
|
|
|
@@ -80,6 +68,7 @@ def get_aws_zone_from_metadata() -> Optional[str]:
|
|
|
80
68
|
try:
|
|
81
69
|
# Use the EC2 metadata service
|
|
82
70
|
import boto
|
|
71
|
+
str(boto) # to prevent removal of the import
|
|
83
72
|
from boto.utils import get_instance_metadata
|
|
84
73
|
logger.debug("Fetch AZ from EC2 metadata")
|
|
85
74
|
return get_instance_metadata()['placement']['availability-zone']
|
toil/lib/aws/ami.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
import time
|
|
5
4
|
import urllib.request
|
|
6
|
-
from
|
|
7
|
-
from
|
|
5
|
+
from typing import Dict, Iterator, Optional, cast
|
|
6
|
+
from urllib.error import HTTPError, URLError
|
|
8
7
|
|
|
9
8
|
from botocore.client import BaseClient
|
|
9
|
+
from botocore.exceptions import ClientError
|
|
10
10
|
|
|
11
11
|
from toil.lib.retry import retry
|
|
12
12
|
|
|
@@ -110,6 +110,12 @@ def flatcar_release_feed_amis(region: str, architecture: str = 'amd64', source:
|
|
|
110
110
|
# Try again
|
|
111
111
|
try_number += 1
|
|
112
112
|
continue
|
|
113
|
+
except URLError:
|
|
114
|
+
# Could be a connection timeout
|
|
115
|
+
logger.exception(f'Failed to retrieve {source} Flatcar release feed JSON')
|
|
116
|
+
# Try again
|
|
117
|
+
try_number += 1
|
|
118
|
+
continue
|
|
113
119
|
if try_number == MAX_TRIES:
|
|
114
120
|
# We could not get the JSON
|
|
115
121
|
logger.error(f'Could not get a readable {source} Flatcar release feed JSON')
|
|
@@ -150,11 +156,18 @@ def feed_flatcar_ami_release(ec2_client: BaseClient, architecture: str = 'amd64'
|
|
|
150
156
|
|
|
151
157
|
for ami in flatcar_release_feed_amis(region, architecture, source):
|
|
152
158
|
# verify it exists on AWS
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
try:
|
|
160
|
+
response = ec2_client.describe_images(Filters=[{'Name': 'image-id', 'Values': [ami]}]) # type: ignore
|
|
161
|
+
if len(response['Images']) == 1 and response['Images'][0]['State'] == 'available':
|
|
162
|
+
return ami
|
|
163
|
+
else:
|
|
164
|
+
logger.warning(f'Flatcar release feed suggests image {ami} which does not exist on AWS in {region}')
|
|
165
|
+
except ClientError:
|
|
166
|
+
# Sometimes we get back nonsense like:
|
|
167
|
+
# botocore.exceptions.ClientError: An error occurred (AuthFailure) when calling the DescribeImages operation: AWS was not able to validate the provided access credentials
|
|
168
|
+
# Don't hold that against the AMI.
|
|
169
|
+
logger.exception(f'Unable to check if AMI {ami} exists on AWS in {region}; assuming it does')
|
|
170
|
+
return ami
|
|
158
171
|
# We didn't find it
|
|
159
172
|
logger.warning(f'Flatcar release feed does not have an image for region {region} that exists on AWS')
|
|
160
173
|
return None
|
|
@@ -162,7 +175,7 @@ def feed_flatcar_ami_release(ec2_client: BaseClient, architecture: str = 'amd64'
|
|
|
162
175
|
|
|
163
176
|
@retry() # TODO: What errors do we get for timeout, JSON parse failure, etc?
|
|
164
177
|
def aws_marketplace_flatcar_ami_search(ec2_client: BaseClient, architecture: str = 'amd64') -> Optional[str]:
|
|
165
|
-
"""Query AWS for all AMI names matching
|
|
178
|
+
"""Query AWS for all AMI names matching ``Flatcar-stable-*`` and return the most recent one."""
|
|
166
179
|
|
|
167
180
|
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_images
|
|
168
181
|
# Possible arch choices on AWS: 'i386'|'x86_64'|'arm64'|'x86_64_mac'
|
toil/lib/aws/iam.py
CHANGED
|
@@ -3,16 +3,15 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from functools import lru_cache
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Dict, List, Optional, Union, cast
|
|
7
7
|
|
|
8
8
|
import boto3
|
|
9
9
|
from mypy_boto3_iam import IAMClient
|
|
10
|
-
from mypy_boto3_iam.type_defs import AttachedPolicyTypeDef
|
|
10
|
+
from mypy_boto3_iam.type_defs import (AttachedPolicyTypeDef,
|
|
11
|
+
PolicyDocumentDictTypeDef)
|
|
11
12
|
from mypy_boto3_sts import STSClient
|
|
12
13
|
|
|
13
|
-
from toil.lib.aws import zone_to_region
|
|
14
14
|
from toil.lib.aws.session import client as get_client
|
|
15
|
-
from toil.provisioners.aws import get_best_aws_zone
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
@@ -121,7 +120,7 @@ def permission_matches_any(perm: str, list_perms: List[str]) -> bool:
|
|
|
121
120
|
return True
|
|
122
121
|
return False
|
|
123
122
|
|
|
124
|
-
def get_actions_from_policy_document(policy_doc:
|
|
123
|
+
def get_actions_from_policy_document(policy_doc: PolicyDocumentDictTypeDef) -> AllowedActionCollection:
|
|
125
124
|
'''
|
|
126
125
|
Given a policy document, go through each statement and create an AllowedActionCollection representing the
|
|
127
126
|
permissions granted in the policy document.
|
|
@@ -138,11 +137,16 @@ def get_actions_from_policy_document(policy_doc: Dict[str, Any]) -> AllowedActio
|
|
|
138
137
|
for resource in statement["Resource"]:
|
|
139
138
|
for key in ["Action", "NotAction"]:
|
|
140
139
|
if key in statement.keys():
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
# mypy_boto3_iam declares policy document as a TypedDict
|
|
141
|
+
# This type expects 4 string keys, of which NotAction is not an option
|
|
142
|
+
# Thus mypy complains. NotAction seems to be valid according to Amazon:
|
|
143
|
+
# https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notaction.html
|
|
144
|
+
# so type: ignore for now
|
|
145
|
+
if isinstance(statement[key], list): # type: ignore[literal-required]
|
|
146
|
+
allowed_actions[resource][key] += statement[key] # type: ignore[literal-required]
|
|
143
147
|
else:
|
|
144
148
|
#Assumes that if it isn't a list it's probably a string
|
|
145
|
-
allowed_actions[resource][key].append(statement[key])
|
|
149
|
+
allowed_actions[resource][key].append(statement[key]) # type: ignore[literal-required]
|
|
146
150
|
|
|
147
151
|
return allowed_actions
|
|
148
152
|
def allowed_actions_attached(iam: IAMClient, attached_policies: List[AttachedPolicyTypeDef]) -> AllowedActionCollection:
|
|
@@ -181,24 +185,28 @@ def allowed_actions_roles(iam: IAMClient, policy_names: List[str], role_name: st
|
|
|
181
185
|
PolicyName=policy_name
|
|
182
186
|
)
|
|
183
187
|
logger.debug("Checking role policy")
|
|
184
|
-
|
|
188
|
+
# PolicyDocument is now a TypedDict, but an instance of TypedDict is not an instance of dict?
|
|
189
|
+
if isinstance(role_policy["PolicyDocument"], str):
|
|
190
|
+
policy_document = json.loads(role_policy["PolicyDocument"])
|
|
191
|
+
else:
|
|
192
|
+
policy_document = role_policy["PolicyDocument"]
|
|
185
193
|
|
|
186
194
|
allowed_actions = add_to_action_collection(allowed_actions, get_actions_from_policy_document(policy_document))
|
|
187
195
|
|
|
188
196
|
return allowed_actions
|
|
189
197
|
|
|
190
198
|
|
|
191
|
-
def collect_policy_actions(policy_documents:
|
|
199
|
+
def collect_policy_actions(policy_documents: List[Union[str, PolicyDocumentDictTypeDef]]) -> AllowedActionCollection:
|
|
192
200
|
"""
|
|
193
201
|
Collect all of the actions allowed by the given policy documents into one AllowedActionCollection.
|
|
194
202
|
"""
|
|
195
203
|
allowed_actions: AllowedActionCollection = init_action_collection()
|
|
196
204
|
for policy_str in policy_documents:
|
|
197
205
|
# sometimes a string is returned from the api, so convert to a dictionary
|
|
198
|
-
if isinstance(policy_str,
|
|
199
|
-
policy_dict = policy_str
|
|
200
|
-
else:
|
|
206
|
+
if isinstance(policy_str, str):
|
|
201
207
|
policy_dict = json.loads(policy_str)
|
|
208
|
+
else:
|
|
209
|
+
policy_dict = policy_str
|
|
202
210
|
allowed_actions = add_to_action_collection(allowed_actions, get_actions_from_policy_document(policy_dict))
|
|
203
211
|
return allowed_actions
|
|
204
212
|
|
toil/lib/aws/session.py
CHANGED
|
@@ -12,24 +12,10 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import collections
|
|
15
|
-
import inspect
|
|
16
15
|
import logging
|
|
17
16
|
import os
|
|
18
|
-
import re
|
|
19
|
-
import socket
|
|
20
17
|
import threading
|
|
21
|
-
from typing import
|
|
22
|
-
Callable,
|
|
23
|
-
Dict,
|
|
24
|
-
Iterable,
|
|
25
|
-
List,
|
|
26
|
-
Optional,
|
|
27
|
-
Tuple,
|
|
28
|
-
TypeVar,
|
|
29
|
-
Union,
|
|
30
|
-
cast)
|
|
31
|
-
from urllib.error import URLError
|
|
32
|
-
from urllib.request import urlopen
|
|
18
|
+
from typing import Dict, Optional, Tuple, cast
|
|
33
19
|
|
|
34
20
|
import boto3
|
|
35
21
|
import boto3.resources.base
|
|
@@ -37,8 +23,8 @@ import boto.connection
|
|
|
37
23
|
import botocore
|
|
38
24
|
from boto3 import Session
|
|
39
25
|
from botocore.client import Config
|
|
40
|
-
from botocore.utils import JSONFileCache
|
|
41
26
|
from botocore.session import get_session
|
|
27
|
+
from botocore.utils import JSONFileCache
|
|
42
28
|
|
|
43
29
|
logger = logging.getLogger(__name__)
|
|
44
30
|
|
toil/lib/aws/utils.py
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import errno
|
|
15
|
-
import json
|
|
16
15
|
import logging
|
|
17
16
|
import os
|
|
18
17
|
import socket
|
|
@@ -21,15 +20,13 @@ from typing import (Any,
|
|
|
21
20
|
Callable,
|
|
22
21
|
ContextManager,
|
|
23
22
|
Dict,
|
|
24
|
-
Hashable,
|
|
25
23
|
Iterable,
|
|
26
24
|
Iterator,
|
|
27
25
|
List,
|
|
28
26
|
Optional,
|
|
29
27
|
Set,
|
|
30
28
|
Union,
|
|
31
|
-
cast
|
|
32
|
-
MutableMapping)
|
|
29
|
+
cast)
|
|
33
30
|
from urllib.parse import ParseResult
|
|
34
31
|
|
|
35
32
|
from toil.lib.aws import session
|
|
@@ -345,6 +342,8 @@ def get_object_for_url(url: ParseResult, existing: Optional[bool] = None) -> "Ob
|
|
|
345
342
|
"""
|
|
346
343
|
Extracts a key (object) from a given parsed s3:// URL.
|
|
347
344
|
|
|
345
|
+
If existing is true and the object does not exist, raises FileNotFoundError.
|
|
346
|
+
|
|
348
347
|
:param bool existing: If True, key is expected to exist. If False, key is expected not to
|
|
349
348
|
exists and it will be created. If None, the key will be created if it doesn't exist.
|
|
350
349
|
"""
|
|
@@ -386,7 +385,7 @@ def get_object_for_url(url: ParseResult, existing: Optional[bool] = None) -> "Ob
|
|
|
386
385
|
else:
|
|
387
386
|
raise
|
|
388
387
|
if existing is True and not objExists:
|
|
389
|
-
raise
|
|
388
|
+
raise FileNotFoundError(f"Key '{key_name}' does not exist in bucket '{bucket_name}'.")
|
|
390
389
|
elif existing is False and objExists:
|
|
391
390
|
raise RuntimeError(f"Key '{key_name}' exists in bucket '{bucket_name}'.")
|
|
392
391
|
|
toil/lib/compatibility.py
CHANGED
toil/lib/conversions.py
CHANGED
|
@@ -46,8 +46,10 @@ def convert_units(num: float,
|
|
|
46
46
|
src_unit: str,
|
|
47
47
|
dst_unit: str = 'B') -> float:
|
|
48
48
|
"""Returns a float representing the converted input in dst_units."""
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if not src_unit.lower() in VALID_PREFIXES:
|
|
50
|
+
raise RuntimeError(f"{src_unit} not a valid unit, valid units are {VALID_PREFIXES}.")
|
|
51
|
+
if not dst_unit.lower() in VALID_PREFIXES:
|
|
52
|
+
raise RuntimeError(f"{dst_unit} not a valid unit, valid units are {VALID_PREFIXES}.")
|
|
51
53
|
return (num * bytes_in_unit(src_unit)) / bytes_in_unit(dst_unit)
|
|
52
54
|
|
|
53
55
|
|
|
@@ -60,7 +62,8 @@ def parse_memory_string(string: str) -> Tuple[float, str]:
|
|
|
60
62
|
# find the first character of the unit
|
|
61
63
|
if character not in '0123456789.-_ ':
|
|
62
64
|
units = string[i:].strip()
|
|
63
|
-
|
|
65
|
+
if not units.lower() in VALID_PREFIXES:
|
|
66
|
+
raise RuntimeError(f"{units} not a valid unit, valid units are {VALID_PREFIXES}.")
|
|
64
67
|
return float(string[:i]), units
|
|
65
68
|
return float(string), 'b'
|
|
66
69
|
|
|
@@ -71,6 +74,7 @@ def human2bytes(string: str) -> int:
|
|
|
71
74
|
integer number of bytes.
|
|
72
75
|
"""
|
|
73
76
|
value, unit = parse_memory_string(string)
|
|
77
|
+
|
|
74
78
|
return int(convert_units(value, src_unit=unit, dst_unit='b'))
|
|
75
79
|
|
|
76
80
|
|
toil/lib/docker.py
CHANGED
|
@@ -17,7 +17,7 @@ import os
|
|
|
17
17
|
import re
|
|
18
18
|
import struct
|
|
19
19
|
from shlex import quote
|
|
20
|
-
from typing import
|
|
20
|
+
from typing import List, Optional
|
|
21
21
|
|
|
22
22
|
import requests
|
|
23
23
|
|
|
@@ -27,7 +27,6 @@ from docker.errors import (ContainerError,
|
|
|
27
27
|
NotFound,
|
|
28
28
|
create_api_error_from_http_exception)
|
|
29
29
|
from docker.utils.socket import consume_socket_output, demux_adaptor
|
|
30
|
-
|
|
31
30
|
from toil.lib.accelerators import get_host_accelerator_numbers
|
|
32
31
|
|
|
33
32
|
logger = logging.getLogger(__name__)
|
|
@@ -84,16 +83,17 @@ def apiDockerCall(job,
|
|
|
84
83
|
jobs, with the intention that failed/orphaned docker jobs be handled
|
|
85
84
|
appropriately.
|
|
86
85
|
|
|
87
|
-
Example of using dockerCall in toil to index a FASTA file with SAMtools
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
Example of using dockerCall in toil to index a FASTA file with SAMtools::
|
|
87
|
+
|
|
88
|
+
def toil_job(job):
|
|
89
|
+
working_dir = job.fileStore.getLocalTempDir()
|
|
90
|
+
path = job.fileStore.readGlobalFile(ref_id,
|
|
91
|
+
os.path.join(working_dir, 'ref.fasta')
|
|
92
|
+
parameters = ['faidx', path]
|
|
93
|
+
apiDockerCall(job,
|
|
94
|
+
image='quay.io/ucgc_cgl/samtools:latest',
|
|
95
|
+
working_dir=working_dir,
|
|
96
|
+
parameters=parameters)
|
|
97
97
|
|
|
98
98
|
Note that when run with detach=False, or with detach=True and stdout=True
|
|
99
99
|
or stderr=True, this is a blocking call. When run with detach=True and
|
|
@@ -103,13 +103,13 @@ def apiDockerCall(job,
|
|
|
103
103
|
:param toil.Job.job job: The Job instance for the calling function.
|
|
104
104
|
:param str image: Name of the Docker image to be used.
|
|
105
105
|
(e.g. 'quay.io/ucsc_cgl/samtools:latest')
|
|
106
|
-
:param list[str] parameters: A list of string elements.
|
|
106
|
+
:param list[str] parameters: A list of string elements. If there are
|
|
107
107
|
multiple elements, these will be joined with
|
|
108
|
-
spaces.
|
|
108
|
+
spaces. This handling of multiple elements
|
|
109
109
|
provides backwards compatibility with previous
|
|
110
110
|
versions which called docker using
|
|
111
111
|
subprocess.check_call().
|
|
112
|
-
|
|
112
|
+
If list of lists: list[list[str]], then treat
|
|
113
113
|
as successive commands chained with pipe.
|
|
114
114
|
:param str working_dir: The working directory.
|
|
115
115
|
:param int deferParam: Action to take on the container upon job completion.
|
|
@@ -225,8 +225,8 @@ def apiDockerCall(job,
|
|
|
225
225
|
working_dir = os.path.abspath(working_dir)
|
|
226
226
|
|
|
227
227
|
# Ensure the user has passed a valid value for deferParam
|
|
228
|
-
|
|
229
|
-
'Please provide a valid value for deferParam.'
|
|
228
|
+
if deferParam not in (None, FORGO, STOP, RM):
|
|
229
|
+
raise RuntimeError('Please provide a valid value for deferParam.')
|
|
230
230
|
|
|
231
231
|
client = docker.from_env(version='auto', timeout=timeout)
|
|
232
232
|
|
|
@@ -413,12 +413,11 @@ def containerIsRunning(container_name: str, timeout: int = 365 * 24 * 60 * 60):
|
|
|
413
413
|
|
|
414
414
|
:param container_name: Name of the container being checked.
|
|
415
415
|
:param int timeout: Use the given timeout in seconds for interactions with
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
essentially indefinitely).
|
|
416
|
+
the Docker daemon. Note that the underlying docker module is not always
|
|
417
|
+
able to abort ongoing reads and writes in order to respect the timeout.
|
|
418
|
+
Defaults to 1 year (i.e. wait essentially indefinitely).
|
|
420
419
|
:returns: True if status is 'running', False if status is anything else,
|
|
421
|
-
|
|
420
|
+
and None if the container does not exist.
|
|
422
421
|
"""
|
|
423
422
|
client = docker.from_env(version='auto', timeout=timeout)
|
|
424
423
|
try:
|
|
@@ -439,7 +438,7 @@ def containerIsRunning(container_name: str, timeout: int = 365 * 24 * 60 * 60):
|
|
|
439
438
|
def getContainerName(job):
|
|
440
439
|
"""
|
|
441
440
|
Create a random string including the job name, and return it. Name will
|
|
442
|
-
match [a-zA-Z0-9][a-zA-Z0-9_.-]
|
|
441
|
+
match ``[a-zA-Z0-9][a-zA-Z0-9_.-]``.
|
|
443
442
|
"""
|
|
444
443
|
parts = ['toil', str(job.description), base64.b64encode(os.urandom(9), b'-_').decode('utf-8')]
|
|
445
444
|
name = re.sub('[^a-zA-Z0-9_.-]', '', '--'.join(parts))
|
toil/lib/ec2.py
CHANGED
|
@@ -103,11 +103,13 @@ def wait_instances_running(ec2, instances: Iterable[Boto2Instance]) -> Iterable[
|
|
|
103
103
|
if i.state == 'pending':
|
|
104
104
|
pending_ids.add(i.id)
|
|
105
105
|
elif i.state == 'running':
|
|
106
|
-
|
|
106
|
+
if i.id in running_ids:
|
|
107
|
+
raise RuntimeError("An instance was already added to the list of running instance IDs. Maybe there is a duplicate.")
|
|
107
108
|
running_ids.add(i.id)
|
|
108
109
|
yield i
|
|
109
110
|
else:
|
|
110
|
-
|
|
111
|
+
if i.id in other_ids:
|
|
112
|
+
raise RuntimeError("An instance was already added to the list of other instances. Maybe there is a duplicate.")
|
|
111
113
|
other_ids.add(i.id)
|
|
112
114
|
yield i
|
|
113
115
|
logger.info('%i instance(s) pending, %i running, %i other.',
|
|
@@ -130,10 +132,10 @@ def wait_spot_requests_active(ec2, requests: Iterable[SpotInstanceRequest], time
|
|
|
130
132
|
:param requests: The requests to wait on.
|
|
131
133
|
|
|
132
134
|
:param timeout: Maximum time in seconds to spend waiting or None to wait forever. If a
|
|
133
|
-
|
|
135
|
+
timeout occurs, the remaining open requests will be cancelled.
|
|
134
136
|
|
|
135
137
|
:param tentative: if True, give up on a spot request at the earliest indication of it
|
|
136
|
-
|
|
138
|
+
not being fulfilled immediately
|
|
137
139
|
|
|
138
140
|
"""
|
|
139
141
|
|
|
@@ -166,11 +168,13 @@ def wait_spot_requests_active(ec2, requests: Iterable[SpotInstanceRequest], time
|
|
|
166
168
|
'Request %s entered status %s indicating that it will not be '
|
|
167
169
|
'fulfilled anytime soon.', r.id, r.status.code)
|
|
168
170
|
elif r.state == 'active':
|
|
169
|
-
|
|
171
|
+
if r.id in active_ids:
|
|
172
|
+
raise RuntimeError("A request was already added to the list of active requests. Maybe there are duplicate requests.")
|
|
170
173
|
active_ids.add(r.id)
|
|
171
174
|
batch.append(r)
|
|
172
175
|
else:
|
|
173
|
-
|
|
176
|
+
if r.id in other_ids:
|
|
177
|
+
raise RuntimeError("A request was already added to the list of other IDs. Maybe there are duplicate requests.")
|
|
174
178
|
other_ids.add(r.id)
|
|
175
179
|
batch.append(r)
|
|
176
180
|
if batch:
|