toil 6.1.0a1__py3-none-any.whl → 7.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 +1 -232
- toil/batchSystems/abstractBatchSystem.py +41 -17
- toil/batchSystems/abstractGridEngineBatchSystem.py +79 -65
- toil/batchSystems/awsBatch.py +8 -8
- toil/batchSystems/cleanup_support.py +7 -3
- toil/batchSystems/contained_executor.py +4 -5
- toil/batchSystems/gridengine.py +1 -1
- toil/batchSystems/htcondor.py +5 -5
- toil/batchSystems/kubernetes.py +25 -11
- toil/batchSystems/local_support.py +3 -3
- toil/batchSystems/lsf.py +9 -9
- toil/batchSystems/mesos/batchSystem.py +4 -4
- toil/batchSystems/mesos/executor.py +3 -2
- toil/batchSystems/options.py +9 -0
- toil/batchSystems/singleMachine.py +11 -10
- toil/batchSystems/slurm.py +129 -16
- toil/batchSystems/torque.py +1 -1
- toil/bus.py +45 -3
- toil/common.py +56 -31
- toil/cwl/cwltoil.py +442 -371
- toil/deferred.py +1 -1
- toil/exceptions.py +1 -1
- toil/fileStores/abstractFileStore.py +69 -20
- toil/fileStores/cachingFileStore.py +6 -22
- toil/fileStores/nonCachingFileStore.py +6 -15
- toil/job.py +270 -86
- toil/jobStores/abstractJobStore.py +37 -31
- toil/jobStores/aws/jobStore.py +280 -218
- toil/jobStores/aws/utils.py +60 -31
- toil/jobStores/conftest.py +2 -2
- toil/jobStores/fileJobStore.py +3 -3
- toil/jobStores/googleJobStore.py +3 -4
- toil/leader.py +89 -38
- toil/lib/aws/__init__.py +26 -10
- toil/lib/aws/iam.py +2 -2
- toil/lib/aws/session.py +62 -22
- toil/lib/aws/utils.py +73 -37
- toil/lib/conversions.py +24 -1
- toil/lib/ec2.py +118 -69
- toil/lib/expando.py +1 -1
- toil/lib/generatedEC2Lists.py +8 -8
- toil/lib/io.py +42 -4
- toil/lib/misc.py +1 -3
- toil/lib/resources.py +57 -16
- toil/lib/retry.py +12 -5
- toil/lib/threading.py +29 -14
- toil/lib/throttle.py +1 -1
- toil/options/common.py +31 -30
- toil/options/wdl.py +5 -0
- toil/provisioners/__init__.py +9 -3
- toil/provisioners/abstractProvisioner.py +12 -2
- toil/provisioners/aws/__init__.py +20 -15
- toil/provisioners/aws/awsProvisioner.py +406 -329
- toil/provisioners/gceProvisioner.py +2 -2
- toil/provisioners/node.py +13 -5
- toil/server/app.py +1 -1
- toil/statsAndLogging.py +93 -23
- toil/test/__init__.py +27 -12
- toil/test/batchSystems/batchSystemTest.py +40 -33
- toil/test/batchSystems/batch_system_plugin_test.py +79 -0
- toil/test/batchSystems/test_slurm.py +22 -7
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +58 -0
- toil/test/cwl/cwlTest.py +245 -236
- toil/test/cwl/seqtk_seq.cwl +1 -1
- toil/test/docs/scriptsTest.py +11 -14
- toil/test/jobStores/jobStoreTest.py +40 -54
- toil/test/lib/aws/test_iam.py +2 -2
- toil/test/lib/test_ec2.py +1 -1
- toil/test/options/__init__.py +13 -0
- toil/test/options/options.py +37 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +51 -34
- toil/test/provisioners/clusterTest.py +99 -16
- toil/test/server/serverTest.py +2 -2
- toil/test/src/autoDeploymentTest.py +1 -1
- toil/test/src/dockerCheckTest.py +2 -1
- toil/test/src/environmentTest.py +125 -0
- toil/test/src/fileStoreTest.py +1 -1
- toil/test/src/jobDescriptionTest.py +18 -8
- toil/test/src/jobTest.py +1 -1
- toil/test/src/realtimeLoggerTest.py +4 -0
- toil/test/src/workerTest.py +52 -19
- toil/test/utils/toilDebugTest.py +62 -4
- toil/test/utils/utilsTest.py +23 -21
- toil/test/wdl/wdltoil_test.py +49 -21
- toil/test/wdl/wdltoil_test_kubernetes.py +77 -0
- toil/toilState.py +68 -9
- toil/utils/toilDebugFile.py +1 -1
- toil/utils/toilDebugJob.py +153 -26
- toil/utils/toilLaunchCluster.py +12 -2
- toil/utils/toilRsyncCluster.py +7 -2
- toil/utils/toilSshCluster.py +7 -3
- toil/utils/toilStats.py +310 -266
- toil/utils/toilStatus.py +98 -52
- toil/version.py +11 -11
- toil/wdl/wdltoil.py +644 -225
- toil/worker.py +125 -83
- {toil-6.1.0a1.dist-info → toil-7.0.0.dist-info}/LICENSE +25 -0
- toil-7.0.0.dist-info/METADATA +158 -0
- {toil-6.1.0a1.dist-info → toil-7.0.0.dist-info}/RECORD +103 -96
- {toil-6.1.0a1.dist-info → toil-7.0.0.dist-info}/WHEEL +1 -1
- toil-6.1.0a1.dist-info/METADATA +0 -125
- {toil-6.1.0a1.dist-info → toil-7.0.0.dist-info}/entry_points.txt +0 -0
- {toil-6.1.0a1.dist-info → toil-7.0.0.dist-info}/top_level.txt +0 -0
|
@@ -11,45 +11,50 @@
|
|
|
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
|
-
|
|
15
14
|
import logging
|
|
16
15
|
import os
|
|
17
16
|
import subprocess
|
|
18
17
|
import time
|
|
18
|
+
|
|
19
19
|
from uuid import uuid4
|
|
20
|
+
from typing import Optional, List
|
|
20
21
|
|
|
21
|
-
from toil.lib.aws import zone_to_region
|
|
22
22
|
from toil.lib.retry import retry
|
|
23
|
+
from toil.test import ToilTest, needs_aws_ec2, needs_fetchable_appliance, slow, needs_env_var
|
|
24
|
+
from toil.lib.aws import zone_to_region
|
|
25
|
+
from toil.lib.aws.session import AWSConnectionManager
|
|
26
|
+
from toil.provisioners import cluster_factory
|
|
23
27
|
from toil.provisioners.aws import get_best_aws_zone
|
|
24
|
-
from toil.test import ToilTest, needs_aws_ec2, needs_fetchable_appliance
|
|
25
28
|
|
|
26
29
|
log = logging.getLogger(__name__)
|
|
27
30
|
|
|
31
|
+
|
|
28
32
|
@needs_aws_ec2
|
|
29
33
|
@needs_fetchable_appliance
|
|
30
34
|
class AbstractClusterTest(ToilTest):
|
|
31
|
-
def __init__(self, methodName):
|
|
35
|
+
def __init__(self, methodName: str) -> None:
|
|
32
36
|
super().__init__(methodName=methodName)
|
|
33
37
|
self.keyName = os.getenv('TOIL_AWS_KEYNAME').strip() or 'id_rsa'
|
|
34
|
-
self.clusterName = 'aws-provisioner-test-
|
|
38
|
+
self.clusterName = f'aws-provisioner-test-{uuid4()}'
|
|
35
39
|
self.leaderNodeType = 't2.medium'
|
|
36
40
|
self.clusterType = 'mesos'
|
|
37
41
|
self.zone = get_best_aws_zone()
|
|
38
42
|
assert self.zone is not None, "Could not determine AWS availability zone to test in; is TOIL_AWS_ZONE set?"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
self.
|
|
43
|
+
self.region = zone_to_region(self.zone)
|
|
44
|
+
|
|
45
|
+
# Get connection to AWS
|
|
46
|
+
self.aws = AWSConnectionManager()
|
|
47
|
+
|
|
43
48
|
# Where should we put our virtualenv?
|
|
44
49
|
self.venvDir = '/tmp/venv'
|
|
45
50
|
|
|
46
|
-
def python(self):
|
|
51
|
+
def python(self) -> str:
|
|
47
52
|
"""
|
|
48
53
|
Return the full path to the venv Python on the leader.
|
|
49
54
|
"""
|
|
50
55
|
return os.path.join(self.venvDir, 'bin/python')
|
|
51
56
|
|
|
52
|
-
def pip(self):
|
|
57
|
+
def pip(self) -> str:
|
|
53
58
|
"""
|
|
54
59
|
Return the full path to the venv pip on the leader.
|
|
55
60
|
"""
|
|
@@ -63,7 +68,7 @@ class AbstractClusterTest(ToilTest):
|
|
|
63
68
|
"""
|
|
64
69
|
subprocess.check_call(['toil', 'destroy-cluster', '-p=aws', '-z', self.zone, self.clusterName])
|
|
65
70
|
|
|
66
|
-
def setUp(self):
|
|
71
|
+
def setUp(self) -> None:
|
|
67
72
|
"""
|
|
68
73
|
Set up for the test.
|
|
69
74
|
Must be overridden to call this method and set self.jobStore.
|
|
@@ -73,13 +78,13 @@ class AbstractClusterTest(ToilTest):
|
|
|
73
78
|
# If this fails, no tests will run.
|
|
74
79
|
self.destroyCluster()
|
|
75
80
|
|
|
76
|
-
def tearDown(self):
|
|
81
|
+
def tearDown(self) -> None:
|
|
77
82
|
# Note that teardown will run even if the test crashes.
|
|
78
83
|
super().tearDown()
|
|
79
84
|
self.destroyCluster()
|
|
80
85
|
subprocess.check_call(['toil', 'clean', self.jobStore])
|
|
81
86
|
|
|
82
|
-
def sshUtil(self, command):
|
|
87
|
+
def sshUtil(self, command: List[str]) -> None:
|
|
83
88
|
"""
|
|
84
89
|
Run the given command on the cluster.
|
|
85
90
|
Raise subprocess.CalledProcessError if it fails.
|
|
@@ -155,7 +160,7 @@ class AbstractClusterTest(ToilTest):
|
|
|
155
160
|
subprocess.check_call(cmd)
|
|
156
161
|
|
|
157
162
|
@retry(errors=[subprocess.CalledProcessError], intervals=[1, 1])
|
|
158
|
-
def createClusterUtil(self, args=None):
|
|
163
|
+
def createClusterUtil(self, args: Optional[List[str]]=None) -> None:
|
|
159
164
|
args = [] if args is None else args
|
|
160
165
|
|
|
161
166
|
command = ['toil', 'launch-cluster', '-p=aws', '-z', self.zone, f'--keyPairName={self.keyName}',
|
|
@@ -167,5 +172,83 @@ class AbstractClusterTest(ToilTest):
|
|
|
167
172
|
subprocess.check_call(command)
|
|
168
173
|
# If we fail, tearDown will destroy the cluster.
|
|
169
174
|
|
|
170
|
-
def launchCluster(self):
|
|
175
|
+
def launchCluster(self) -> None:
|
|
171
176
|
self.createClusterUtil()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@needs_aws_ec2
|
|
180
|
+
@needs_fetchable_appliance
|
|
181
|
+
@slow
|
|
182
|
+
class CWLOnARMTest(AbstractClusterTest):
|
|
183
|
+
"""Run the CWL 1.2 conformance tests on ARM specifically."""
|
|
184
|
+
def __init__(self, methodName: str) -> None:
|
|
185
|
+
super().__init__(methodName=methodName)
|
|
186
|
+
self.clusterName = f'cwl-test-{uuid4()}'
|
|
187
|
+
self.leaderNodeType = "t4g.2xlarge"
|
|
188
|
+
self.clusterType = "kubernetes"
|
|
189
|
+
# We need to be running in a directory which Flatcar and the Toil Appliance both have
|
|
190
|
+
self.cwl_test_dir = "/tmp/toil/cwlTests"
|
|
191
|
+
|
|
192
|
+
def setUp(self) -> None:
|
|
193
|
+
super().setUp()
|
|
194
|
+
self.jobStore = f"aws:{self.awsRegion()}:cluster-{uuid4()}"
|
|
195
|
+
|
|
196
|
+
@needs_env_var("CI_COMMIT_SHA", "a git commit sha")
|
|
197
|
+
def test_cwl_on_arm(self) -> None:
|
|
198
|
+
# Make a cluster
|
|
199
|
+
self.launchCluster()
|
|
200
|
+
# get the leader so we know the IP address - we don't need to wait since create cluster
|
|
201
|
+
# already ensures the leader is running
|
|
202
|
+
self.cluster = cluster_factory(
|
|
203
|
+
provisioner="aws", zone=self.zone, clusterName=self.clusterName
|
|
204
|
+
)
|
|
205
|
+
self.leader = self.cluster.getLeader()
|
|
206
|
+
|
|
207
|
+
commit = os.environ["CI_COMMIT_SHA"]
|
|
208
|
+
self.sshUtil(
|
|
209
|
+
[
|
|
210
|
+
"bash",
|
|
211
|
+
"-c",
|
|
212
|
+
f"mkdir -p {self.cwl_test_dir} && cd {self.cwl_test_dir} && git clone https://github.com/DataBiosphere/toil.git",
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# We use CI_COMMIT_SHA to retrieve the Toil version needed to run the CWL tests
|
|
217
|
+
self.sshUtil(
|
|
218
|
+
["bash", "-c", f"cd {self.cwl_test_dir}/toil && git checkout {commit}"]
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# --never-download prevents silent upgrades to pip, wheel and setuptools
|
|
222
|
+
self.sshUtil(
|
|
223
|
+
[
|
|
224
|
+
"bash",
|
|
225
|
+
"-c",
|
|
226
|
+
f"virtualenv --system-site-packages --never-download {self.venvDir}",
|
|
227
|
+
]
|
|
228
|
+
)
|
|
229
|
+
self.sshUtil(
|
|
230
|
+
[
|
|
231
|
+
"bash",
|
|
232
|
+
"-c",
|
|
233
|
+
f". .{self.venvDir}/bin/activate && cd {self.cwl_test_dir}/toil && make prepare && make develop extras=[all]",
|
|
234
|
+
]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Runs the CWLv12Test on an ARM instance
|
|
238
|
+
self.sshUtil(
|
|
239
|
+
[
|
|
240
|
+
"bash",
|
|
241
|
+
"-c",
|
|
242
|
+
f". .{self.venvDir}/bin/activate && cd {self.cwl_test_dir}/toil && pytest --log-cli-level DEBUG -r s src/toil/test/cwl/cwlTest.py::CWLv12Test::test_run_conformance",
|
|
243
|
+
]
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# We know if it succeeds it should save a junit XML for us to read.
|
|
247
|
+
# Bring it back to be an artifact.
|
|
248
|
+
self.rsync_util(
|
|
249
|
+
f":{self.cwl_test_dir}/toil/conformance-1.2.junit.xml",
|
|
250
|
+
os.path.join(
|
|
251
|
+
self._projectRootPath(),
|
|
252
|
+
"arm-conformance-1.2.junit.xml"
|
|
253
|
+
)
|
|
254
|
+
)
|
toil/test/server/serverTest.py
CHANGED
|
@@ -575,9 +575,9 @@ class ToilWESServerWorkflowTest(AbstractToilWESServerTest):
|
|
|
575
575
|
with self.app.test_client() as client:
|
|
576
576
|
rv = client.post("/ga4gh/wes/v1/runs", data={
|
|
577
577
|
"workflow_url": "https://raw.githubusercontent.com/DataBiosphere/toil/releases/5.4.x/src/toil"
|
|
578
|
-
"/test/
|
|
578
|
+
"/test/cwl/echo.cwl",
|
|
579
579
|
"workflow_type": "CWL",
|
|
580
|
-
"workflow_type_version": "v1.
|
|
580
|
+
"workflow_type_version": "v1.2",
|
|
581
581
|
"workflow_params": json.dumps({"message": "Hello, world!"}),
|
|
582
582
|
})
|
|
583
583
|
# workflow is submitted successfully
|
|
@@ -243,7 +243,7 @@ class AutoDeploymentTest(ApplianceTestSupport):
|
|
|
243
243
|
|
|
244
244
|
3) it is an instance of Job (and so does not introduce the user script to sys.path itself),
|
|
245
245
|
|
|
246
|
-
… it might cause problems with deserializing a
|
|
246
|
+
… it might cause problems with deserializing a deferred function defined in the user script.
|
|
247
247
|
|
|
248
248
|
`Encapsulated` has two children to ensure that `Follow-on` is run in a separate worker.
|
|
249
249
|
"""
|
toil/test/src/dockerCheckTest.py
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import unittest
|
|
15
15
|
|
|
16
16
|
from docker.errors import ImageNotFound
|
|
17
|
-
from toil import checkDockerImageExists, parseDockerAppliance
|
|
17
|
+
from toil import checkDockerImageExists, parseDockerAppliance, retry
|
|
18
18
|
from toil.test import ToilTest, needs_docker
|
|
19
19
|
|
|
20
20
|
|
|
@@ -76,6 +76,7 @@ class DockerCheckTest(ToilTest):
|
|
|
76
76
|
google_repo = 'gcr.io/google-containers/busybox:latest'
|
|
77
77
|
assert checkDockerImageExists(google_repo)
|
|
78
78
|
|
|
79
|
+
@retry(errors=[TimeoutError]) # see: https://github.com/DataBiosphere/toil/issues/4902
|
|
79
80
|
def testBadGoogleRepo(self):
|
|
80
81
|
"""Bad repo and tag. This should raise."""
|
|
81
82
|
nonexistent_google_repo = 'gcr.io/google-containers/--------:---'
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Copyright (C) 2015-2024 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
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
|
|
19
|
+
from argparse import Namespace
|
|
20
|
+
from threading import Thread
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
from toil.common import Toil
|
|
24
|
+
from toil.job import Job
|
|
25
|
+
from toil.test import ToilTest, slow
|
|
26
|
+
from toil.jobStores.abstractJobStore import NoSuchFileException
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
class EnvironmentTest(ToilTest):
|
|
31
|
+
"""
|
|
32
|
+
Test to make sure that Toil's environment variable save and restore system
|
|
33
|
+
(environment.pickle) works.
|
|
34
|
+
|
|
35
|
+
The environment should be captured once at the start of the workflow and
|
|
36
|
+
should be sent through based on that, not base don the leader's current
|
|
37
|
+
environment when the job is launched.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def test_environment(self):
|
|
41
|
+
options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
|
|
42
|
+
options.logLevel = "DEBUG"
|
|
43
|
+
options.retryCount = 0
|
|
44
|
+
|
|
45
|
+
main(options)
|
|
46
|
+
|
|
47
|
+
def signal_leader(job):
|
|
48
|
+
"""
|
|
49
|
+
Make a file in the file store that the leader can see.
|
|
50
|
+
"""
|
|
51
|
+
with job.fileStore.jobStore.write_shared_file_stream("jobstarted.txt", encoding="utf-8") as stream:
|
|
52
|
+
stream.write("Job has run")
|
|
53
|
+
|
|
54
|
+
def check_environment(job, try_name: str):
|
|
55
|
+
"""
|
|
56
|
+
Fail if the test environment is wrong.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
job.fileStore.log_to_leader(f"Try {try_name} checking environment")
|
|
60
|
+
value = os.environ["MAGIC_ENV_VAR_123"]
|
|
61
|
+
job.fileStore.log_to_leader(f"Try {try_name} got: {value}")
|
|
62
|
+
if value != "Value1":
|
|
63
|
+
raise RuntimeError("Environment variable is wrong!")
|
|
64
|
+
|
|
65
|
+
def wait_a_bit(job):
|
|
66
|
+
"""
|
|
67
|
+
Toil job that waits.
|
|
68
|
+
"""
|
|
69
|
+
time.sleep(10)
|
|
70
|
+
|
|
71
|
+
def check_environment_repeatedly(job):
|
|
72
|
+
"""
|
|
73
|
+
Toil job that checks the environment, waits, and checks it again, as
|
|
74
|
+
separate invocations.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
signal = job.addChildJobFn(signal_leader)
|
|
78
|
+
check1 = signal.addFollowOnJobFn(check_environment, "try1")
|
|
79
|
+
waiter = check1.addFollowOnJobFn(wait_a_bit)
|
|
80
|
+
check2 = waiter.addFollowOnJobFn(check_environment, "try2")
|
|
81
|
+
# Add another one to make sure we don't chain
|
|
82
|
+
check3 = waiter.addFollowOnJobFn(check_environment, "try3")
|
|
83
|
+
|
|
84
|
+
def main(options: Optional[Namespace] = None):
|
|
85
|
+
"""
|
|
86
|
+
Run the actual workflow with the given options.
|
|
87
|
+
"""
|
|
88
|
+
if not options:
|
|
89
|
+
# deal with command line arguments
|
|
90
|
+
parser = Job.Runner.getDefaultArgumentParser()
|
|
91
|
+
options = parser.parse_args()
|
|
92
|
+
logging.basicConfig()
|
|
93
|
+
|
|
94
|
+
# Set something that should be seen by Toil jobs
|
|
95
|
+
os.environ["MAGIC_ENV_VAR_123"] = "Value1"
|
|
96
|
+
|
|
97
|
+
with Toil(options) as toil:
|
|
98
|
+
|
|
99
|
+
# Get a tthe job store so we can use shared files.
|
|
100
|
+
jobStore = toil._jobStore
|
|
101
|
+
|
|
102
|
+
# Once the workflow has started, change the environment
|
|
103
|
+
def change_environment_later():
|
|
104
|
+
"""
|
|
105
|
+
After waiting, modify the environment.
|
|
106
|
+
"""
|
|
107
|
+
while True:
|
|
108
|
+
# Wait for the workflow to say it ran something
|
|
109
|
+
time.sleep(5)
|
|
110
|
+
try:
|
|
111
|
+
with jobStore.read_shared_file_stream("jobstarted.txt", encoding="utf-8") as stream:
|
|
112
|
+
logger.info("Got signal from job: %s", stream.read().strip())
|
|
113
|
+
break
|
|
114
|
+
except NoSuchFileException:
|
|
115
|
+
pass
|
|
116
|
+
# Change the environment variable
|
|
117
|
+
logger.info("Changing environment variable")
|
|
118
|
+
os.environ["MAGIC_ENV_VAR_123"] = "Value2"
|
|
119
|
+
changer_thread = Thread(target=change_environment_later)
|
|
120
|
+
changer_thread.start()
|
|
121
|
+
|
|
122
|
+
toil.start(Job.wrapJobFn(check_environment_repeatedly))
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|
toil/test/src/fileStoreTest.py
CHANGED
|
@@ -641,7 +641,7 @@ class hidden:
|
|
|
641
641
|
file into cache then rewrites it to the job store triggering an async write since the
|
|
642
642
|
two unique jobstore IDs point to the same local file. Also, the second write is not
|
|
643
643
|
cached since the first was written to cache, and there "isn't enough space" to cache the
|
|
644
|
-
second.
|
|
644
|
+
second. Immediately assert that the second write isn't cached, and is being
|
|
645
645
|
asynchronously written to the job store.
|
|
646
646
|
|
|
647
647
|
Attempting to get the file from the jobstore should not fail.
|
|
@@ -18,6 +18,7 @@ from configargparse import ArgumentParser
|
|
|
18
18
|
|
|
19
19
|
from toil.common import Toil
|
|
20
20
|
from toil.job import Job, JobDescription, TemporaryID
|
|
21
|
+
from toil.resource import ModuleDescriptor
|
|
21
22
|
from toil.test import ToilTest
|
|
22
23
|
|
|
23
24
|
|
|
@@ -43,17 +44,24 @@ class JobDescriptionTest(ToilTest):
|
|
|
43
44
|
Tests the public interface of a JobDescription.
|
|
44
45
|
"""
|
|
45
46
|
|
|
46
|
-
command = "by your command"
|
|
47
47
|
memory = 2^32
|
|
48
48
|
disk = 2^32
|
|
49
49
|
cores = "1"
|
|
50
50
|
preemptible = 1
|
|
51
51
|
|
|
52
|
-
j = JobDescription(
|
|
52
|
+
j = JobDescription(requirements={"memory": memory, "cores": cores, "disk": disk, "preemptible": preemptible},
|
|
53
53
|
jobName='testJobGraph', unitName='noName')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Without a body, and with nothing to run, nextSuccessors will be None
|
|
57
|
+
self.assertEqual(j.has_body(), False)
|
|
58
|
+
self.assertEqual(j.nextSuccessors(), None)
|
|
59
|
+
|
|
60
|
+
# Attach a body so the job has something to do itself.
|
|
61
|
+
j.attach_body("fake", ModuleDescriptor.forModule("toil"))
|
|
62
|
+
self.assertEqual(j.has_body(), True)
|
|
54
63
|
|
|
55
64
|
#Check attributes
|
|
56
|
-
self.assertEqual(j.command, command)
|
|
57
65
|
self.assertEqual(j.memory, memory)
|
|
58
66
|
self.assertEqual(j.disk, disk)
|
|
59
67
|
self.assertEqual(j.cores, int(cores))
|
|
@@ -68,22 +76,24 @@ class JobDescriptionTest(ToilTest):
|
|
|
68
76
|
self.assertEqual(j.logJobStoreFileID, None)
|
|
69
77
|
|
|
70
78
|
#Check equals function (should be based on object identity and not contents)
|
|
71
|
-
j2 = JobDescription(
|
|
79
|
+
j2 = JobDescription(requirements={"memory": memory, "cores": cores, "disk": disk, "preemptible": preemptible},
|
|
72
80
|
jobName='testJobGraph', unitName='noName')
|
|
81
|
+
j2.attach_body("fake", ModuleDescriptor.forModule("toil"))
|
|
73
82
|
self.assertNotEqual(j, j2)
|
|
74
83
|
###TODO test other functionality
|
|
75
84
|
|
|
76
85
|
def testJobDescriptionSequencing(self):
|
|
77
|
-
j = JobDescription(
|
|
86
|
+
j = JobDescription(requirements={}, jobName='unimportant')
|
|
78
87
|
|
|
79
88
|
j.addChild('child')
|
|
80
89
|
j.addFollowOn('followOn')
|
|
81
90
|
|
|
82
|
-
# With a
|
|
91
|
+
# With a body, nothing should be ready to run
|
|
92
|
+
j.attach_body("fake", ModuleDescriptor.forModule("toil"))
|
|
83
93
|
self.assertEqual(list(j.nextSuccessors()), [])
|
|
84
94
|
|
|
85
|
-
# With
|
|
86
|
-
j.
|
|
95
|
+
# With body cleared, child should be ready to run
|
|
96
|
+
j.detach_body()
|
|
87
97
|
self.assertEqual(list(j.nextSuccessors()), ['child'])
|
|
88
98
|
|
|
89
99
|
# Without the child, the follow-on should be ready to run
|
toil/test/src/jobTest.py
CHANGED
|
@@ -268,7 +268,7 @@ class JobTest(ToilTest):
|
|
|
268
268
|
Test for issue #1465: Detection of checkpoint jobs that are not leaf vertices
|
|
269
269
|
identifies leaf vertices incorrectly
|
|
270
270
|
|
|
271
|
-
Test verification of new checkpoint jobs being leaf
|
|
271
|
+
Test verification of new checkpoint jobs being leaf vertices,
|
|
272
272
|
starting with the following baseline workflow::
|
|
273
273
|
|
|
274
274
|
Parent
|
|
@@ -30,6 +30,9 @@ class RealtimeLoggerTest(ToilTest):
|
|
|
30
30
|
# Set up a log message detector to the root logger
|
|
31
31
|
logging.getLogger().addHandler(detector)
|
|
32
32
|
|
|
33
|
+
# I believe coloredlogs replaces handlers with its own when doing handler formatting, preserving only filters
|
|
34
|
+
# https://github.com/xolox/python-coloredlogs/blob/65bdfe976ac0bf81e8c0bd9a98242b9d666b2859/coloredlogs/__init__.py#L453-L459
|
|
35
|
+
options.colored_logs = False
|
|
33
36
|
Job.Runner.startToil(LogTest(), options)
|
|
34
37
|
|
|
35
38
|
# We need the message we're supposed to see
|
|
@@ -46,6 +49,7 @@ class MessageDetector(logging.StreamHandler):
|
|
|
46
49
|
def __init__(self):
|
|
47
50
|
self.detected = False # Have we seen the message we want?
|
|
48
51
|
self.overLogged = False # Have we seen the message we don't want?
|
|
52
|
+
|
|
49
53
|
super().__init__()
|
|
50
54
|
|
|
51
55
|
def emit(self, record):
|
toil/test/src/workerTest.py
CHANGED
|
@@ -18,6 +18,8 @@ from toil.jobStores.fileJobStore import FileJobStore
|
|
|
18
18
|
from toil.test import ToilTest
|
|
19
19
|
from toil.worker import nextChainable
|
|
20
20
|
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
class WorkerTests(ToilTest):
|
|
23
25
|
"""Test miscellaneous units of the worker."""
|
|
@@ -32,7 +34,7 @@ class WorkerTests(ToilTest):
|
|
|
32
34
|
|
|
33
35
|
def testNextChainable(self):
|
|
34
36
|
"""Make sure chainable/non-chainable jobs are identified correctly."""
|
|
35
|
-
def createTestJobDesc(memory, cores, disk, preemptible, checkpoint):
|
|
37
|
+
def createTestJobDesc(memory, cores, disk, preemptible: bool = True, checkpoint: bool = False, local: Optional[bool] = None):
|
|
36
38
|
"""
|
|
37
39
|
Create a JobDescription with no command (representing a Job that
|
|
38
40
|
has already run) and return the JobDescription.
|
|
@@ -41,7 +43,16 @@ class WorkerTests(ToilTest):
|
|
|
41
43
|
self.jobNumber += 1
|
|
42
44
|
|
|
43
45
|
descClass = CheckpointJobDescription if checkpoint else JobDescription
|
|
44
|
-
jobDesc = descClass(
|
|
46
|
+
jobDesc = descClass(
|
|
47
|
+
requirements={
|
|
48
|
+
'memory': memory,
|
|
49
|
+
'cores': cores,
|
|
50
|
+
'disk': disk,
|
|
51
|
+
'preemptible': preemptible
|
|
52
|
+
},
|
|
53
|
+
jobName=name,
|
|
54
|
+
local=local
|
|
55
|
+
)
|
|
45
56
|
|
|
46
57
|
# Assign an ID
|
|
47
58
|
self.jobStore.assign_job_id(jobDesc)
|
|
@@ -53,42 +64,64 @@ class WorkerTests(ToilTest):
|
|
|
53
64
|
# Try with the branch point at both child and follow-on stages
|
|
54
65
|
|
|
55
66
|
# Identical non-checkpoint jobs should be chainable.
|
|
56
|
-
jobDesc1 = createTestJobDesc(1, 2, 3
|
|
57
|
-
jobDesc2 = createTestJobDesc(1, 2, 3
|
|
67
|
+
jobDesc1 = createTestJobDesc(1, 2, 3)
|
|
68
|
+
jobDesc2 = createTestJobDesc(1, 2, 3)
|
|
58
69
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
59
70
|
chainable = nextChainable(jobDesc1, self.jobStore, self.config)
|
|
60
71
|
self.assertNotEqual(chainable, None)
|
|
61
|
-
self.assertEqual(
|
|
72
|
+
self.assertEqual(chainable.jobStoreID, jobDesc2.jobStoreID)
|
|
62
73
|
|
|
63
74
|
# Identical checkpoint jobs should not be chainable.
|
|
64
|
-
jobDesc1 = createTestJobDesc(1, 2, 3, True
|
|
65
|
-
jobDesc2 = createTestJobDesc(1, 2, 3, True
|
|
75
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, checkpoint=True)
|
|
76
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, checkpoint=True)
|
|
77
|
+
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
78
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
79
|
+
|
|
80
|
+
# Changing checkpoint from false to true should make it not chainable.
|
|
81
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, checkpoint=False)
|
|
82
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, checkpoint=True)
|
|
66
83
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
67
|
-
self.assertEqual(
|
|
84
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
68
85
|
|
|
69
86
|
# If there is no child we should get nothing to chain.
|
|
70
|
-
jobDesc1 = createTestJobDesc(1, 2, 3
|
|
71
|
-
self.assertEqual(
|
|
87
|
+
jobDesc1 = createTestJobDesc(1, 2, 3)
|
|
88
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
72
89
|
|
|
73
90
|
# If there are 2 or more children we should get nothing to chain.
|
|
74
|
-
jobDesc1 = createTestJobDesc(1, 2, 3
|
|
75
|
-
jobDesc2 = createTestJobDesc(1, 2, 3
|
|
76
|
-
jobDesc3 = createTestJobDesc(1, 2, 3
|
|
91
|
+
jobDesc1 = createTestJobDesc(1, 2, 3)
|
|
92
|
+
jobDesc2 = createTestJobDesc(1, 2, 3)
|
|
93
|
+
jobDesc3 = createTestJobDesc(1, 2, 3)
|
|
77
94
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
78
95
|
getattr(jobDesc1, successorType)(jobDesc3.jobStoreID)
|
|
79
|
-
self.assertEqual(
|
|
96
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
80
97
|
|
|
81
98
|
# If there is an increase in resource requirements we should get nothing to chain.
|
|
82
|
-
|
|
99
|
+
base_reqs = {'memory': 1, 'cores': 2, 'disk': 3, 'preemptible': True, 'checkpoint': False}
|
|
83
100
|
for increased_attribute in ('memory', 'cores', 'disk'):
|
|
101
|
+
reqs = dict(base_reqs)
|
|
84
102
|
jobDesc1 = createTestJobDesc(**reqs)
|
|
85
103
|
reqs[increased_attribute] += 1
|
|
86
104
|
jobDesc2 = createTestJobDesc(**reqs)
|
|
87
105
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
88
|
-
self.assertEqual(
|
|
106
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
89
107
|
|
|
90
108
|
# A change in preemptability from True to False should be disallowed.
|
|
91
|
-
jobDesc1 = createTestJobDesc(1, 2, 3, True
|
|
92
|
-
jobDesc2 = createTestJobDesc(1, 2, 3, False
|
|
109
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, preemptible=True)
|
|
110
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, preemptible=False)
|
|
111
|
+
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
112
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
113
|
+
|
|
114
|
+
# A change in local-ness from True to False should be disallowed.
|
|
115
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, local=True)
|
|
116
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, local=False)
|
|
93
117
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
94
|
-
self.assertEqual(
|
|
118
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
119
|
+
|
|
120
|
+
# A change in local-ness from False to True should be allowed,
|
|
121
|
+
# since running locally is an optional optimization.
|
|
122
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, local=False)
|
|
123
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, local=True)
|
|
124
|
+
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
125
|
+
chainable = nextChainable(jobDesc1, self.jobStore, self.config)
|
|
126
|
+
self.assertNotEqual(chainable, None)
|
|
127
|
+
self.assertEqual(chainable.jobStoreID, jobDesc2.jobStoreID)
|
toil/test/utils/toilDebugTest.py
CHANGED
|
@@ -21,7 +21,7 @@ import pytest
|
|
|
21
21
|
from toil.test import ToilTest
|
|
22
22
|
|
|
23
23
|
from toil.lib.resources import glob
|
|
24
|
-
from toil.test import slow
|
|
24
|
+
from toil.test import slow, needs_wdl
|
|
25
25
|
from toil.version import python
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
@@ -136,13 +136,13 @@ class DebugJobTest(ToilTest):
|
|
|
136
136
|
|
|
137
137
|
logger.info("Running workflow that always fails")
|
|
138
138
|
try:
|
|
139
|
-
# Run an always-
|
|
139
|
+
# Run an always-failing workflow
|
|
140
140
|
subprocess.check_call([
|
|
141
141
|
python,
|
|
142
142
|
os.path.abspath("src/toil/test/docs/scripts/example_alwaysfail.py"),
|
|
143
143
|
"--retryCount=0",
|
|
144
144
|
"--logCritical",
|
|
145
|
-
"--disableProgress
|
|
145
|
+
"--disableProgress",
|
|
146
146
|
job_store
|
|
147
147
|
], stderr=subprocess.DEVNULL)
|
|
148
148
|
raise RuntimeError("Failing workflow succeeded!")
|
|
@@ -150,13 +150,44 @@ class DebugJobTest(ToilTest):
|
|
|
150
150
|
# Should fail to run
|
|
151
151
|
logger.info("Task failed successfully")
|
|
152
152
|
pass
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
# Get the job ID.
|
|
155
155
|
# TODO: This assumes a lot about the FileJobStore. Use the MessageBus instead?
|
|
156
156
|
job_id = "kind-explode/" + os.listdir(os.path.join(job_store, "jobs/kind-explode"))[0]
|
|
157
157
|
|
|
158
158
|
return job_store, job_id
|
|
159
159
|
|
|
160
|
+
def _get_wdl_job_store_and_job_name(self):
|
|
161
|
+
"""
|
|
162
|
+
Get a job store and the name of a failed job in it that actually wanted to use some files.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
# First make a job store.
|
|
166
|
+
job_store = os.path.join(self._createTempDir(), "tree")
|
|
167
|
+
|
|
168
|
+
logger.info("Running workflow that always fails")
|
|
169
|
+
try:
|
|
170
|
+
# Run an always-failing workflow
|
|
171
|
+
subprocess.check_call([
|
|
172
|
+
"toil-wdl-runner",
|
|
173
|
+
os.path.abspath("src/toil/test/docs/scripts/example_alwaysfail_with_files.wdl"),
|
|
174
|
+
"--retryCount=0",
|
|
175
|
+
"--logCritical",
|
|
176
|
+
"--disableProgress",
|
|
177
|
+
"--jobStore",
|
|
178
|
+
job_store
|
|
179
|
+
], stderr=subprocess.DEVNULL)
|
|
180
|
+
raise RuntimeError("Failing workflow succeeded!")
|
|
181
|
+
except subprocess.CalledProcessError:
|
|
182
|
+
# Should fail to run
|
|
183
|
+
logger.info("Task failed successfully")
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
# Get a job name for a job that fails
|
|
187
|
+
job_name = "WDLTaskJob"
|
|
188
|
+
|
|
189
|
+
return job_store, job_name
|
|
190
|
+
|
|
160
191
|
def test_run_job(self):
|
|
161
192
|
"""
|
|
162
193
|
Make sure that we can use toil debug-job to try and run a job in-process.
|
|
@@ -198,4 +229,31 @@ class DebugJobTest(ToilTest):
|
|
|
198
229
|
job_id
|
|
199
230
|
])
|
|
200
231
|
|
|
232
|
+
@needs_wdl
|
|
233
|
+
def test_retrieve_task_directory(self):
|
|
234
|
+
"""
|
|
235
|
+
Make sure that we can use --retrieveTaskDirectory to get the input files for a job.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
job_store, job_name = self._get_wdl_job_store_and_job_name()
|
|
239
|
+
|
|
240
|
+
logger.info("Trying to retrieve task dorectory for job %s", job_name)
|
|
241
|
+
|
|
242
|
+
dest_dir = os.path.join(self._createTempDir(), "dump")
|
|
243
|
+
|
|
244
|
+
# Print the job info and make sure that doesn't crash.
|
|
245
|
+
subprocess.check_call([
|
|
246
|
+
"toil",
|
|
247
|
+
"debug-job",
|
|
248
|
+
"--logDebug",
|
|
249
|
+
job_store,
|
|
250
|
+
job_name,
|
|
251
|
+
"--retrieveTaskDirectory",
|
|
252
|
+
dest_dir
|
|
253
|
+
])
|
|
254
|
+
|
|
255
|
+
first_file = os.path.join(dest_dir, "inside/mnt/miniwdl_task_container/work/_miniwdl_inputs/0/test.txt")
|
|
256
|
+
assert os.path.exists(first_file), "Input file not found in fake container environment"
|
|
257
|
+
self.assertEqual(open(first_file).read(), "These are the contents\n")
|
|
258
|
+
|
|
201
259
|
|