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/test/src/fileStoreTest.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import collections
|
|
15
15
|
import datetime
|
|
16
16
|
import errno
|
|
17
|
+
import fcntl
|
|
17
18
|
import filecmp
|
|
18
19
|
import inspect
|
|
19
20
|
import logging
|
|
@@ -31,16 +32,20 @@ import pytest
|
|
|
31
32
|
from toil.common import Toil
|
|
32
33
|
from toil.exceptions import FailedJobsException
|
|
33
34
|
from toil.fileStores import FileID
|
|
34
|
-
from toil.fileStores.cachingFileStore import (
|
|
35
|
-
|
|
35
|
+
from toil.fileStores.cachingFileStore import (
|
|
36
|
+
CacheUnbalancedError,
|
|
37
|
+
IllegalDeletionCacheError,
|
|
38
|
+
)
|
|
36
39
|
from toil.job import Job
|
|
37
40
|
from toil.jobStores.abstractJobStore import NoSuchFileException
|
|
38
41
|
from toil.realtimeLogger import RealtimeLogger
|
|
39
|
-
from toil.test import (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
from toil.test import (
|
|
43
|
+
ToilTest,
|
|
44
|
+
needs_aws_ec2,
|
|
45
|
+
needs_google_project,
|
|
46
|
+
needs_google_storage,
|
|
47
|
+
slow,
|
|
48
|
+
)
|
|
44
49
|
|
|
45
50
|
# Some tests take too long on the AWS jobstore and are unquitable for CI. They can be
|
|
46
51
|
# be run during manual tests by setting this to False.
|
|
@@ -54,41 +59,43 @@ class hidden:
|
|
|
54
59
|
Hiding the abstract test classes from the Unittest loader so it can be inherited in different
|
|
55
60
|
test suites for the different job stores.
|
|
56
61
|
"""
|
|
62
|
+
|
|
57
63
|
class AbstractFileStoreTest(ToilTest, metaclass=ABCMeta):
|
|
58
64
|
"""
|
|
59
65
|
An abstract base class for testing the various general functions described in
|
|
60
66
|
:class:toil.fileStores.abstractFileStore.AbstractFileStore
|
|
61
67
|
"""
|
|
68
|
+
|
|
62
69
|
# This is overwritten in the inheriting classs
|
|
63
70
|
jobStoreType = None
|
|
64
71
|
|
|
65
72
|
def _getTestJobStore(self):
|
|
66
|
-
if self.jobStoreType ==
|
|
73
|
+
if self.jobStoreType == "file":
|
|
67
74
|
return self._getTestJobStorePath()
|
|
68
|
-
elif self.jobStoreType ==
|
|
69
|
-
return f
|
|
70
|
-
elif self.jobStoreType ==
|
|
71
|
-
projectID = os.getenv(
|
|
72
|
-
return f
|
|
75
|
+
elif self.jobStoreType == "aws":
|
|
76
|
+
return f"aws:{self.awsRegion()}:cache-tests-{str(uuid4())}"
|
|
77
|
+
elif self.jobStoreType == "google":
|
|
78
|
+
projectID = os.getenv("TOIL_GOOGLE_PROJECTID")
|
|
79
|
+
return f"google:{projectID}:cache-tests-{str(uuid4())}"
|
|
73
80
|
else:
|
|
74
|
-
raise RuntimeError(
|
|
81
|
+
raise RuntimeError("Illegal job store type.")
|
|
75
82
|
|
|
76
83
|
def setUp(self):
|
|
77
84
|
super().setUp()
|
|
78
85
|
self.work_dir = self._createTempDir()
|
|
79
86
|
self.options = Job.Runner.getDefaultOptions(self._getTestJobStore())
|
|
80
|
-
self.options.logLevel =
|
|
87
|
+
self.options.logLevel = "DEBUG"
|
|
81
88
|
self.options.realTimeLogging = True
|
|
82
89
|
self.options.workDir = self.work_dir
|
|
83
|
-
self.options.clean =
|
|
84
|
-
self.options.logFile = os.path.join(self.work_dir,
|
|
90
|
+
self.options.clean = "always"
|
|
91
|
+
self.options.logFile = os.path.join(self.work_dir, "logFile")
|
|
85
92
|
|
|
86
93
|
self.tmp_dir = self._createTempDir()
|
|
87
94
|
|
|
88
95
|
def create_file(self, content, executable=False):
|
|
89
|
-
file_path = f
|
|
96
|
+
file_path = f"{self.tmp_dir}/{uuid4()}"
|
|
90
97
|
|
|
91
|
-
with open(file_path,
|
|
98
|
+
with open(file_path, "w") as f:
|
|
92
99
|
f.write(content)
|
|
93
100
|
|
|
94
101
|
if executable:
|
|
@@ -131,6 +138,7 @@ class hidden:
|
|
|
131
138
|
A logging handler that watches for a certain substring and
|
|
132
139
|
trips a flag if it appears.
|
|
133
140
|
"""
|
|
141
|
+
|
|
134
142
|
def __init__(self, match: str):
|
|
135
143
|
super().__init__()
|
|
136
144
|
self.match = match
|
|
@@ -144,8 +152,7 @@ class hidden:
|
|
|
144
152
|
|
|
145
153
|
logging.getLogger().addHandler(handler)
|
|
146
154
|
|
|
147
|
-
F = Job.wrapJobFn(self._accessAndFail,
|
|
148
|
-
disk='100M')
|
|
155
|
+
F = Job.wrapJobFn(self._accessAndFail, disk="100M")
|
|
149
156
|
try:
|
|
150
157
|
Job.Runner.startToil(F, self.options)
|
|
151
158
|
except FailedJobsException:
|
|
@@ -154,19 +161,20 @@ class hidden:
|
|
|
154
161
|
|
|
155
162
|
logging.getLogger().removeHandler(handler)
|
|
156
163
|
|
|
157
|
-
assert
|
|
164
|
+
assert (
|
|
165
|
+
handler.seen
|
|
166
|
+
), "Downloaded file name not found in logs of failing Toil run"
|
|
158
167
|
|
|
159
168
|
@staticmethod
|
|
160
169
|
def _accessAndFail(job):
|
|
161
170
|
with job.fileStore.writeGlobalFileStream() as (writable, file_id):
|
|
162
|
-
writable.write(b
|
|
163
|
-
localPath = os.path.join(job.fileStore.getLocalTempDir(),
|
|
171
|
+
writable.write(b"Cats")
|
|
172
|
+
localPath = os.path.join(job.fileStore.getLocalTempDir(), "cats.txt")
|
|
164
173
|
job.fileStore.readGlobalFile(file_id, localPath)
|
|
165
174
|
with job.fileStore.readGlobalFileStream(file_id) as readable:
|
|
166
175
|
pass
|
|
167
176
|
raise RuntimeError("I do not like this file")
|
|
168
177
|
|
|
169
|
-
|
|
170
178
|
# Test filestore operations. This is a slightly less intense version of the cache specific
|
|
171
179
|
# test `testReturnFileSizes`
|
|
172
180
|
@slow
|
|
@@ -175,10 +183,13 @@ class hidden:
|
|
|
175
183
|
Write a couple of files to the jobstore. Delete a couple of them. Read back written
|
|
176
184
|
and locally deleted files.
|
|
177
185
|
"""
|
|
178
|
-
workdir = self._createTempDir(purpose=
|
|
179
|
-
F = Job.wrapJobFn(
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
187
|
+
F = Job.wrapJobFn(
|
|
188
|
+
self._testFileStoreOperations,
|
|
189
|
+
nonLocalDir=workdir,
|
|
190
|
+
numIters=30,
|
|
191
|
+
disk="2G",
|
|
192
|
+
)
|
|
182
193
|
Job.Runner.startToil(F, self.options)
|
|
183
194
|
|
|
184
195
|
@staticmethod
|
|
@@ -192,14 +203,15 @@ class hidden:
|
|
|
192
203
|
# Add one file for the sake of having something in the job store
|
|
193
204
|
writeFileSize = random.randint(0, 30)
|
|
194
205
|
cls = hidden.AbstractNonCachingFileStoreTest
|
|
195
|
-
fsId, _ = cls._writeFileToJobStore(
|
|
196
|
-
|
|
206
|
+
fsId, _ = cls._writeFileToJobStore(
|
|
207
|
+
job, isLocalFile=True, nonLocalDir=nonLocalDir, fileMB=writeFileSize
|
|
208
|
+
)
|
|
197
209
|
|
|
198
210
|
# Fill in the size of the local file we just made
|
|
199
211
|
writtenFiles[fsId] = writeFileSize
|
|
200
212
|
# Remember it actually should be local
|
|
201
213
|
localFileIDs.add(fsId)
|
|
202
|
-
logger.info(
|
|
214
|
+
logger.info("Now have local file: %s", fsId)
|
|
203
215
|
|
|
204
216
|
i = 0
|
|
205
217
|
while i <= numIters:
|
|
@@ -207,13 +219,21 @@ class hidden:
|
|
|
207
219
|
if randVal < 0.33: # Write
|
|
208
220
|
writeFileSize = random.randint(0, 30)
|
|
209
221
|
isLocalFile = True if random.random() <= 0.5 else False
|
|
210
|
-
fsID, _ = cls._writeFileToJobStore(
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
fsID, _ = cls._writeFileToJobStore(
|
|
223
|
+
job,
|
|
224
|
+
isLocalFile=isLocalFile,
|
|
225
|
+
nonLocalDir=nonLocalDir,
|
|
226
|
+
fileMB=writeFileSize,
|
|
227
|
+
)
|
|
213
228
|
writtenFiles[fsID] = writeFileSize
|
|
214
229
|
if isLocalFile:
|
|
215
230
|
localFileIDs.add(fsID)
|
|
216
|
-
logger.info(
|
|
231
|
+
logger.info(
|
|
232
|
+
"Wrote %s file of size %d MB: %s",
|
|
233
|
+
"local" if isLocalFile else "non-local",
|
|
234
|
+
writeFileSize,
|
|
235
|
+
fsID,
|
|
236
|
+
)
|
|
217
237
|
else:
|
|
218
238
|
if len(writtenFiles) == 0:
|
|
219
239
|
continue
|
|
@@ -223,10 +243,19 @@ class hidden:
|
|
|
223
243
|
if randVal < 0.66: # Read
|
|
224
244
|
mutable = True if random.random() <= 0.5 else False
|
|
225
245
|
cache = True if random.random() <= 0.5 else False
|
|
226
|
-
job.fileStore.readGlobalFile(
|
|
227
|
-
|
|
246
|
+
job.fileStore.readGlobalFile(
|
|
247
|
+
fsID,
|
|
248
|
+
"/".join([work_dir, str(uuid4())]),
|
|
249
|
+
cache=cache,
|
|
250
|
+
mutable=mutable,
|
|
251
|
+
)
|
|
228
252
|
localFileIDs.add(fsID)
|
|
229
|
-
logger.info(
|
|
253
|
+
logger.info(
|
|
254
|
+
"Read %s %s local copy of: %s",
|
|
255
|
+
"mutable" if mutable else "immutable",
|
|
256
|
+
"cached" if cache else "uncached",
|
|
257
|
+
fsID,
|
|
258
|
+
)
|
|
230
259
|
else: # Delete
|
|
231
260
|
if rdelRandVal <= 0.5: # Local Delete
|
|
232
261
|
if fsID not in localFileIDs:
|
|
@@ -239,18 +268,23 @@ class hidden:
|
|
|
239
268
|
# ENOENT. If it doesn't something is
|
|
240
269
|
# broken.
|
|
241
270
|
raise
|
|
242
|
-
logger.info(
|
|
271
|
+
logger.info(
|
|
272
|
+
"Correctly fail to local-delete non-local file: %s",
|
|
273
|
+
fsID,
|
|
274
|
+
)
|
|
243
275
|
else:
|
|
244
|
-
assert
|
|
276
|
+
assert (
|
|
277
|
+
False
|
|
278
|
+
), f"Was able to delete non-local file {fsID}"
|
|
245
279
|
else:
|
|
246
|
-
logger.info(
|
|
280
|
+
logger.info("Delete local file: %s", fsID)
|
|
247
281
|
job.fileStore.deleteLocalFile(fsID)
|
|
248
282
|
else: # Global Delete
|
|
249
283
|
job.fileStore.deleteGlobalFile(fsID)
|
|
250
284
|
writtenFiles.pop(fsID)
|
|
251
285
|
if fsID in localFileIDs:
|
|
252
286
|
localFileIDs.remove(fsID)
|
|
253
|
-
logger.info(
|
|
287
|
+
logger.info("No longer have file: %s", fsID)
|
|
254
288
|
i += 1
|
|
255
289
|
|
|
256
290
|
def testWriteReadGlobalFilePermissions(self):
|
|
@@ -261,18 +295,23 @@ class hidden:
|
|
|
261
295
|
"""
|
|
262
296
|
for executable in True, False:
|
|
263
297
|
for caching in True, False:
|
|
264
|
-
with self.subTest(
|
|
265
|
-
|
|
266
|
-
|
|
298
|
+
with self.subTest(
|
|
299
|
+
f"Testing readwrite file permissions\n"
|
|
300
|
+
f"[executable: {executable}]\n"
|
|
301
|
+
f"[caching: {caching}]\n"
|
|
302
|
+
):
|
|
267
303
|
self.options.caching = caching
|
|
268
|
-
read_write_job = Job.wrapJobFn(
|
|
304
|
+
read_write_job = Job.wrapJobFn(
|
|
305
|
+
self._testWriteReadGlobalFilePermissions,
|
|
306
|
+
executable=executable,
|
|
307
|
+
)
|
|
269
308
|
Job.Runner.startToil(read_write_job, self.options)
|
|
270
309
|
|
|
271
310
|
@staticmethod
|
|
272
311
|
def _testWriteReadGlobalFilePermissions(job, executable):
|
|
273
312
|
srcFile = job.fileStore.getLocalTempFile()
|
|
274
|
-
with open(srcFile,
|
|
275
|
-
f.write(
|
|
313
|
+
with open(srcFile, "w") as f:
|
|
314
|
+
f.write("Hello")
|
|
276
315
|
|
|
277
316
|
if executable:
|
|
278
317
|
os.chmod(srcFile, os.stat(srcFile).st_mode | stat.S_IXUSR)
|
|
@@ -284,10 +323,14 @@ class hidden:
|
|
|
284
323
|
for mutable in True, False:
|
|
285
324
|
for symlink in True, False:
|
|
286
325
|
dstFile = job.fileStore.getLocalTempFileName()
|
|
287
|
-
job.fileStore.readGlobalFile(
|
|
326
|
+
job.fileStore.readGlobalFile(
|
|
327
|
+
fileID, userPath=dstFile, mutable=mutable, symlink=symlink
|
|
328
|
+
)
|
|
288
329
|
# Current file owner execute permissions
|
|
289
330
|
currentPermissions = os.stat(dstFile).st_mode & stat.S_IXUSR
|
|
290
|
-
assert
|
|
331
|
+
assert (
|
|
332
|
+
initialPermissions == currentPermissions
|
|
333
|
+
), f"{initialPermissions} != {currentPermissions}"
|
|
291
334
|
|
|
292
335
|
def testWriteExportFileCompatibility(self):
|
|
293
336
|
"""
|
|
@@ -295,20 +338,24 @@ class hidden:
|
|
|
295
338
|
when they are exported from the leader.
|
|
296
339
|
"""
|
|
297
340
|
for executable in True, False:
|
|
298
|
-
export_file_job = Job.wrapJobFn(
|
|
341
|
+
export_file_job = Job.wrapJobFn(
|
|
342
|
+
self._testWriteExportFileCompatibility, executable=executable
|
|
343
|
+
)
|
|
299
344
|
with Toil(self.options) as toil:
|
|
300
345
|
initialPermissions, fileID = toil.start(export_file_job)
|
|
301
346
|
dstFile = os.path.join(self._createTempDir(), str(uuid4()))
|
|
302
|
-
toil.exportFile(fileID,
|
|
347
|
+
toil.exportFile(fileID, "file://" + dstFile)
|
|
303
348
|
currentPermissions = os.stat(dstFile).st_mode & stat.S_IXUSR
|
|
304
349
|
|
|
305
|
-
assert
|
|
350
|
+
assert (
|
|
351
|
+
initialPermissions == currentPermissions
|
|
352
|
+
), f"{initialPermissions} != {currentPermissions}"
|
|
306
353
|
|
|
307
354
|
@staticmethod
|
|
308
355
|
def _testWriteExportFileCompatibility(job, executable):
|
|
309
356
|
srcFile = job.fileStore.getLocalTempFile()
|
|
310
|
-
with open(srcFile,
|
|
311
|
-
f.write(
|
|
357
|
+
with open(srcFile, "w") as f:
|
|
358
|
+
f.write("Hello")
|
|
312
359
|
if executable:
|
|
313
360
|
os.chmod(srcFile, os.stat(srcFile).st_mode | stat.S_IXUSR)
|
|
314
361
|
initialPermissions = os.stat(srcFile).st_mode & stat.S_IXUSR
|
|
@@ -322,22 +369,30 @@ class hidden:
|
|
|
322
369
|
"""
|
|
323
370
|
with Toil(self.options) as toil:
|
|
324
371
|
for executable in True, False:
|
|
325
|
-
file_path = self.create_file(content=
|
|
372
|
+
file_path = self.create_file(content="Hello", executable=executable)
|
|
326
373
|
initial_permissions = os.stat(file_path).st_mode & stat.S_IXUSR
|
|
327
|
-
file_id = toil.importFile(f
|
|
374
|
+
file_id = toil.importFile(f"file://{file_path}")
|
|
328
375
|
for mutable in True, False:
|
|
329
376
|
for symlink in True, False:
|
|
330
|
-
with self.subTest(
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
377
|
+
with self.subTest(
|
|
378
|
+
f"Now testing readGlobalFileWith: mutable={mutable} symlink={symlink}"
|
|
379
|
+
):
|
|
380
|
+
A = Job.wrapJobFn(
|
|
381
|
+
self._testImportReadFileCompatibility,
|
|
382
|
+
fileID=file_id,
|
|
383
|
+
initialPermissions=initial_permissions,
|
|
384
|
+
mutable=mutable,
|
|
385
|
+
symlink=symlink,
|
|
386
|
+
)
|
|
336
387
|
toil.start(A)
|
|
337
388
|
|
|
338
389
|
@staticmethod
|
|
339
|
-
def _testImportReadFileCompatibility(
|
|
340
|
-
|
|
390
|
+
def _testImportReadFileCompatibility(
|
|
391
|
+
job, fileID, initialPermissions, mutable, symlink
|
|
392
|
+
):
|
|
393
|
+
dstFile = job.fileStore.readGlobalFile(
|
|
394
|
+
fileID, mutable=mutable, symlink=symlink
|
|
395
|
+
)
|
|
341
396
|
currentPermissions = os.stat(dstFile).st_mode & stat.S_IXUSR
|
|
342
397
|
|
|
343
398
|
assert initialPermissions == currentPermissions
|
|
@@ -352,11 +407,16 @@ class hidden:
|
|
|
352
407
|
|
|
353
408
|
@staticmethod
|
|
354
409
|
def _testReadWriteFileStreamTextMode(job):
|
|
355
|
-
with job.fileStore.writeGlobalFileStream(encoding=
|
|
356
|
-
stream
|
|
410
|
+
with job.fileStore.writeGlobalFileStream(encoding="utf-8") as (
|
|
411
|
+
stream,
|
|
412
|
+
fileID,
|
|
413
|
+
):
|
|
414
|
+
stream.write("foo")
|
|
357
415
|
job.fileStore.readGlobalFileStream(fileID)
|
|
358
|
-
with job.fileStore.readGlobalFileStream(
|
|
359
|
-
|
|
416
|
+
with job.fileStore.readGlobalFileStream(
|
|
417
|
+
fileID, encoding="utf-8"
|
|
418
|
+
) as stream2:
|
|
419
|
+
assert "foo" == stream2.read()
|
|
360
420
|
|
|
361
421
|
@staticmethod
|
|
362
422
|
def _writeFileToJobStore(job, isLocalFile, nonLocalDir=None, fileMB=1):
|
|
@@ -373,7 +433,7 @@ class hidden:
|
|
|
373
433
|
else:
|
|
374
434
|
assert nonLocalDir is not None
|
|
375
435
|
work_dir = nonLocalDir
|
|
376
|
-
with open(os.path.join(work_dir, str(uuid4())),
|
|
436
|
+
with open(os.path.join(work_dir, str(uuid4())), "wb") as testFile:
|
|
377
437
|
testFile.write(os.urandom(fileMB * 1024 * 1024))
|
|
378
438
|
|
|
379
439
|
return job.fileStore.writeGlobalFile(testFile.name), testFile
|
|
@@ -408,7 +468,7 @@ class hidden:
|
|
|
408
468
|
the chain. This tests whether the cache is created properly even when the job crashes
|
|
409
469
|
randomly.
|
|
410
470
|
"""
|
|
411
|
-
if testingIsAutomatic and self.jobStoreType !=
|
|
471
|
+
if testingIsAutomatic and self.jobStoreType != "file":
|
|
412
472
|
self.skipTest("To save time")
|
|
413
473
|
self.options.retryCount = 10
|
|
414
474
|
self.options.badWorker = 0.25
|
|
@@ -435,7 +495,7 @@ class hidden:
|
|
|
435
495
|
|
|
436
496
|
# Explicitly set clean to always so even the failed cases get cleaned (This will
|
|
437
497
|
# overwrite the value set in setUp if it is ever changed in the future)
|
|
438
|
-
self.options.clean =
|
|
498
|
+
self.options.clean = "always"
|
|
439
499
|
|
|
440
500
|
self._testCacheEviction(file1MB=20, file2MB=30, diskRequestMB=10)
|
|
441
501
|
|
|
@@ -451,7 +511,7 @@ class hidden:
|
|
|
451
511
|
|
|
452
512
|
# Explicitly set clean to always so even the failed cases get cleaned (This will
|
|
453
513
|
# overwrite the value set in setUp if it is ever changed in the future)
|
|
454
|
-
self.options.clean =
|
|
514
|
+
self.options.clean = "always"
|
|
455
515
|
|
|
456
516
|
self._testCacheEviction(file1MB=20, file2MB=30, diskRequestMB=30)
|
|
457
517
|
|
|
@@ -467,7 +527,7 @@ class hidden:
|
|
|
467
527
|
|
|
468
528
|
# Explicitly set clean to always so even the failed cases get cleaned (This will
|
|
469
529
|
# overwrite the value set in setUp if it is ever changed in the future)
|
|
470
|
-
self.options.clean =
|
|
530
|
+
self.options.clean = "always"
|
|
471
531
|
|
|
472
532
|
self._testCacheEviction(file1MB=20, file2MB=30, diskRequestMB=60)
|
|
473
533
|
|
|
@@ -475,7 +535,7 @@ class hidden:
|
|
|
475
535
|
# If the job store and cache are on the same file system, file
|
|
476
536
|
# sizes are accounted for by the job store and are not reflected in
|
|
477
537
|
# the cache hence this test is redundant (caching will be free).
|
|
478
|
-
if not self.options.jobStore.startswith((
|
|
538
|
+
if not self.options.jobStore.startswith(("aws", "google")):
|
|
479
539
|
workDirDev = os.stat(self.options.workDir).st_dev
|
|
480
540
|
if self.options.jobStore.startswith("file:"):
|
|
481
541
|
# Before #4538, options.jobStore would have the raw path while the Config object would prepend the
|
|
@@ -483,11 +543,15 @@ class hidden:
|
|
|
483
543
|
# The options namespace and the Config object now have the exact same behavior
|
|
484
544
|
# which means parse_jobstore will be called with argparse rather than with the config object
|
|
485
545
|
# so remove the prepended file: scheme
|
|
486
|
-
jobStoreDev = os.stat(
|
|
546
|
+
jobStoreDev = os.stat(
|
|
547
|
+
os.path.dirname(self.options.jobStore[5:])
|
|
548
|
+
).st_dev
|
|
487
549
|
else:
|
|
488
550
|
jobStoreDev = os.stat(os.path.dirname(self.options.jobStore)).st_dev
|
|
489
551
|
if workDirDev == jobStoreDev:
|
|
490
|
-
self.skipTest(
|
|
552
|
+
self.skipTest(
|
|
553
|
+
"Job store and working directory are on the same filesystem."
|
|
554
|
+
)
|
|
491
555
|
|
|
492
556
|
def _testCacheEviction(self, file1MB, file2MB, diskRequestMB):
|
|
493
557
|
"""
|
|
@@ -500,23 +564,35 @@ class hidden:
|
|
|
500
564
|
self.options.retryCount = 0
|
|
501
565
|
if diskRequestMB > 50:
|
|
502
566
|
# This can be non int as it will never reach _probeJobReqs
|
|
503
|
-
expectedResult =
|
|
567
|
+
expectedResult = "Fail"
|
|
504
568
|
else:
|
|
505
569
|
expectedResult = 50 - file1MB if diskRequestMB <= file1MB else 0
|
|
506
570
|
try:
|
|
507
|
-
A = Job.wrapJobFn(
|
|
508
|
-
|
|
571
|
+
A = Job.wrapJobFn(
|
|
572
|
+
self._writeFileToJobStoreWithAsserts,
|
|
573
|
+
isLocalFile=True,
|
|
574
|
+
fileMB=file1MB,
|
|
575
|
+
)
|
|
509
576
|
# Sleep for 1 second after writing the first file so that their ctimes are
|
|
510
577
|
# guaranteed to be distinct for the purpose of this test.
|
|
511
578
|
B = Job.wrapJobFn(self._sleepy, timeToSleep=1)
|
|
512
|
-
C = Job.wrapJobFn(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
579
|
+
C = Job.wrapJobFn(
|
|
580
|
+
self._writeFileToJobStoreWithAsserts,
|
|
581
|
+
isLocalFile=True,
|
|
582
|
+
fileMB=file2MB,
|
|
583
|
+
)
|
|
584
|
+
D = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=50, disk="0Mi")
|
|
585
|
+
E = Job.wrapJobFn(
|
|
586
|
+
self._uselessFunc, disk="".join([str(diskRequestMB), "Mi"])
|
|
587
|
+
)
|
|
516
588
|
# Set it to > 2GB such that the cleanup jobs don't die in the non-fail cases
|
|
517
|
-
F = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=5000, disk=
|
|
518
|
-
G = Job.wrapJobFn(
|
|
519
|
-
|
|
589
|
+
F = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=5000, disk="10Mi")
|
|
590
|
+
G = Job.wrapJobFn(
|
|
591
|
+
self._probeJobReqs,
|
|
592
|
+
sigmaJob=100,
|
|
593
|
+
cached=expectedResult,
|
|
594
|
+
disk="100Mi",
|
|
595
|
+
)
|
|
520
596
|
A.addChild(B)
|
|
521
597
|
B.addChild(C)
|
|
522
598
|
C.addChild(D)
|
|
@@ -528,12 +604,16 @@ class hidden:
|
|
|
528
604
|
with open(self.options.logFile) as f:
|
|
529
605
|
logContents = f.read()
|
|
530
606
|
if CacheUnbalancedError.message in logContents:
|
|
531
|
-
self.assertEqual(expectedResult,
|
|
607
|
+
self.assertEqual(expectedResult, "Fail")
|
|
532
608
|
else:
|
|
533
|
-
self.fail(
|
|
609
|
+
self.fail(
|
|
610
|
+
"Toil did not raise the expected CacheUnbalancedError but failed for some other reason"
|
|
611
|
+
)
|
|
534
612
|
|
|
535
613
|
@staticmethod
|
|
536
|
-
def _writeFileToJobStoreWithAsserts(
|
|
614
|
+
def _writeFileToJobStoreWithAsserts(
|
|
615
|
+
job, isLocalFile, nonLocalDir=None, fileMB=1, expectAsyncUpload=True
|
|
616
|
+
):
|
|
537
617
|
"""
|
|
538
618
|
This function creates a file and writes it to the jobstore.
|
|
539
619
|
|
|
@@ -547,7 +627,9 @@ class hidden:
|
|
|
547
627
|
the job store later(T) or immediately(F)
|
|
548
628
|
"""
|
|
549
629
|
cls = hidden.AbstractNonCachingFileStoreTest
|
|
550
|
-
fsID, testFile = cls._writeFileToJobStore(
|
|
630
|
+
fsID, testFile = cls._writeFileToJobStore(
|
|
631
|
+
job, isLocalFile, nonLocalDir, fileMB
|
|
632
|
+
)
|
|
551
633
|
actual = os.stat(testFile.name).st_nlink
|
|
552
634
|
|
|
553
635
|
# If the caching is free, the job store must have hard links to
|
|
@@ -565,13 +647,19 @@ class hidden:
|
|
|
565
647
|
# We also expect a link in the job store
|
|
566
648
|
expected += 1
|
|
567
649
|
|
|
568
|
-
assert actual == expected,
|
|
650
|
+
assert actual == expected, "Should have %d links. Got %d." % (
|
|
651
|
+
expected,
|
|
652
|
+
actual,
|
|
653
|
+
)
|
|
569
654
|
|
|
570
|
-
logger.info(
|
|
655
|
+
logger.info("Uploaded %s with %d links", fsID, actual)
|
|
571
656
|
|
|
572
657
|
if not isLocalFile:
|
|
573
658
|
# Make sure it isn't cached if we don't want it to be
|
|
574
|
-
assert not job.fileStore.fileIsCached(fsID),
|
|
659
|
+
assert not job.fileStore.fileIsCached(fsID), (
|
|
660
|
+
"File uploaded from non-local-temp directory %s should not be cached"
|
|
661
|
+
% nonLocalDir
|
|
662
|
+
)
|
|
575
663
|
|
|
576
664
|
return fsID
|
|
577
665
|
|
|
@@ -608,30 +696,36 @@ class hidden:
|
|
|
608
696
|
:param int sigmaJob: Expected sum of job requirements in MB.
|
|
609
697
|
"""
|
|
610
698
|
|
|
611
|
-
RealtimeLogger.info(
|
|
699
|
+
RealtimeLogger.info("Probing job requirements")
|
|
612
700
|
|
|
613
701
|
valueDict = locals()
|
|
614
|
-
assert
|
|
702
|
+
assert total or cached or sigmaJob
|
|
615
703
|
|
|
616
704
|
# Work out which function to call for which value
|
|
617
|
-
toCall = {
|
|
618
|
-
|
|
619
|
-
|
|
705
|
+
toCall = {
|
|
706
|
+
"total": job.fileStore.getCacheLimit,
|
|
707
|
+
"cached": job.fileStore.getCacheUsed,
|
|
708
|
+
"sigmaJob": job.fileStore.getCacheExtraJobSpace,
|
|
709
|
+
}
|
|
620
710
|
|
|
621
|
-
for value in (
|
|
711
|
+
for value in ("total", "cached", "sigmaJob"):
|
|
622
712
|
# If the value wasn't provided, it is None and should be ignored
|
|
623
713
|
if valueDict[value] is None:
|
|
624
714
|
continue
|
|
625
715
|
|
|
626
|
-
RealtimeLogger.info(
|
|
716
|
+
RealtimeLogger.info("Probing cache state: %s", value)
|
|
627
717
|
|
|
628
718
|
expectedBytes = valueDict[value] * 1024 * 1024
|
|
629
719
|
cacheInfoBytes = toCall[value]()
|
|
630
720
|
|
|
631
|
-
RealtimeLogger.info(
|
|
721
|
+
RealtimeLogger.info(
|
|
722
|
+
"Got %d for %s; expected %d", cacheInfoBytes, value, expectedBytes
|
|
723
|
+
)
|
|
632
724
|
|
|
633
|
-
assert cacheInfoBytes == expectedBytes,
|
|
634
|
-
|
|
725
|
+
assert cacheInfoBytes == expectedBytes, (
|
|
726
|
+
"Testing %s: Expected " % value
|
|
727
|
+
+ f"{expectedBytes} but got {cacheInfoBytes}."
|
|
728
|
+
)
|
|
635
729
|
|
|
636
730
|
@slow
|
|
637
731
|
def testAsyncWriteWithCaching(self):
|
|
@@ -641,7 +735,7 @@ class hidden:
|
|
|
641
735
|
file into cache then rewrites it to the job store triggering an async write since the
|
|
642
736
|
two unique jobstore IDs point to the same local file. Also, the second write is not
|
|
643
737
|
cached since the first was written to cache, and there "isn't enough space" to cache the
|
|
644
|
-
second.
|
|
738
|
+
second. Immediately assert that the second write isn't cached, and is being
|
|
645
739
|
asynchronously written to the job store.
|
|
646
740
|
|
|
647
741
|
Attempting to get the file from the jobstore should not fail.
|
|
@@ -649,12 +743,14 @@ class hidden:
|
|
|
649
743
|
print("Testing")
|
|
650
744
|
logger.debug("Testing testing 123")
|
|
651
745
|
self.options.retryCount = 0
|
|
652
|
-
self.options.logLevel =
|
|
653
|
-
A = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=1024, disk=
|
|
654
|
-
B = Job.wrapJobFn(self._doubleWriteFileToJobStore, fileMB=850, disk=
|
|
655
|
-
C = Job.wrapJobFn(
|
|
746
|
+
self.options.logLevel = "DEBUG"
|
|
747
|
+
A = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=1024, disk="1G")
|
|
748
|
+
B = Job.wrapJobFn(self._doubleWriteFileToJobStore, fileMB=850, disk="900M")
|
|
749
|
+
C = Job.wrapJobFn(
|
|
750
|
+
self._readFromJobStoreWithoutAssertions, fsID=B.rv(), disk="1G"
|
|
751
|
+
)
|
|
656
752
|
# Set it to > 2GB such that the cleanup jobs don't die.
|
|
657
|
-
D = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=5000, disk=
|
|
753
|
+
D = Job.wrapJobFn(self._adjustCacheLimit, newTotalMB=5000, disk="1G")
|
|
658
754
|
A.addChild(B)
|
|
659
755
|
B.addChild(C)
|
|
660
756
|
C.addChild(D)
|
|
@@ -670,20 +766,22 @@ class hidden:
|
|
|
670
766
|
:param fileMB: File Size
|
|
671
767
|
:return: Job store file ID for second written file
|
|
672
768
|
"""
|
|
673
|
-
job.fileStore.log_to_leader(
|
|
769
|
+
job.fileStore.log_to_leader("Double writing a file into job store")
|
|
674
770
|
work_dir = job.fileStore.getLocalTempDir()
|
|
675
|
-
with open(os.path.join(work_dir, str(uuid4())),
|
|
771
|
+
with open(os.path.join(work_dir, str(uuid4())), "wb") as testFile:
|
|
676
772
|
testFile.write(os.urandom(fileMB * 1024 * 1024))
|
|
677
773
|
|
|
678
|
-
job.fileStore.log_to_leader(
|
|
774
|
+
job.fileStore.log_to_leader("Writing copy 1 and discarding ID")
|
|
679
775
|
job.fileStore.writeGlobalFile(testFile.name)
|
|
680
|
-
job.fileStore.log_to_leader(
|
|
776
|
+
job.fileStore.log_to_leader("Writing copy 2 and saving ID")
|
|
681
777
|
fsID = job.fileStore.writeGlobalFile(testFile.name)
|
|
682
|
-
job.fileStore.log_to_leader(f
|
|
778
|
+
job.fileStore.log_to_leader(f"Copy 2 ID: {fsID}")
|
|
683
779
|
|
|
684
|
-
hidden.AbstractCachingFileStoreTest._readFromJobStoreWithoutAssertions(
|
|
780
|
+
hidden.AbstractCachingFileStoreTest._readFromJobStoreWithoutAssertions(
|
|
781
|
+
job, fsID
|
|
782
|
+
)
|
|
685
783
|
|
|
686
|
-
job.fileStore.log_to_leader(
|
|
784
|
+
job.fileStore.log_to_leader("Writing copy 3 and returning ID")
|
|
687
785
|
return job.fileStore.writeGlobalFile(testFile.name)
|
|
688
786
|
|
|
689
787
|
@staticmethod
|
|
@@ -695,7 +793,7 @@ class hidden:
|
|
|
695
793
|
:param fsID: Job store file ID for the read file
|
|
696
794
|
:return: None
|
|
697
795
|
"""
|
|
698
|
-
job.fileStore.log_to_leader(
|
|
796
|
+
job.fileStore.log_to_leader("Reading the written file")
|
|
699
797
|
job.fileStore.readGlobalFile(fsID)
|
|
700
798
|
|
|
701
799
|
# writeGlobalFile tests
|
|
@@ -705,9 +803,12 @@ class hidden:
|
|
|
705
803
|
Write a file not in localTempDir to the job store. Such a file should not be cached.
|
|
706
804
|
Ensure the file is not cached.
|
|
707
805
|
"""
|
|
708
|
-
workdir = self._createTempDir(purpose=
|
|
709
|
-
A = Job.wrapJobFn(
|
|
710
|
-
|
|
806
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
807
|
+
A = Job.wrapJobFn(
|
|
808
|
+
self._writeFileToJobStoreWithAsserts,
|
|
809
|
+
isLocalFile=False,
|
|
810
|
+
nonLocalDir=workdir,
|
|
811
|
+
)
|
|
711
812
|
Job.Runner.startToil(A, self.options)
|
|
712
813
|
|
|
713
814
|
def testWriteLocalFileToJobStore(self):
|
|
@@ -740,11 +841,18 @@ class hidden:
|
|
|
740
841
|
|
|
741
842
|
:param cacheReadFile: Does the read file need to be cached(T) or not(F)
|
|
742
843
|
"""
|
|
743
|
-
workdir = self._createTempDir(purpose=
|
|
744
|
-
A = Job.wrapJobFn(
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
844
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
845
|
+
A = Job.wrapJobFn(
|
|
846
|
+
self._writeFileToJobStoreWithAsserts,
|
|
847
|
+
isLocalFile=False,
|
|
848
|
+
nonLocalDir=workdir,
|
|
849
|
+
)
|
|
850
|
+
B = Job.wrapJobFn(
|
|
851
|
+
self._readFromJobStore,
|
|
852
|
+
isCachedFile=False,
|
|
853
|
+
cacheReadFile=cacheReadFile,
|
|
854
|
+
fsID=A.rv(),
|
|
855
|
+
)
|
|
748
856
|
A.addChild(B)
|
|
749
857
|
Job.Runner.startToil(A, self.options)
|
|
750
858
|
|
|
@@ -767,25 +875,38 @@ class hidden:
|
|
|
767
875
|
work_dir = job.fileStore.getLocalTempDir()
|
|
768
876
|
wantHardLink = False
|
|
769
877
|
if isCachedFile:
|
|
770
|
-
outfile = job.fileStore.readGlobalFile(
|
|
771
|
-
|
|
878
|
+
outfile = job.fileStore.readGlobalFile(
|
|
879
|
+
fsID, "/".join([work_dir, "temp"]), mutable=False
|
|
880
|
+
)
|
|
772
881
|
wantHardLink = True
|
|
773
882
|
else:
|
|
774
883
|
if cacheReadFile:
|
|
775
|
-
outfile = job.fileStore.readGlobalFile(
|
|
776
|
-
|
|
884
|
+
outfile = job.fileStore.readGlobalFile(
|
|
885
|
+
fsID, "/".join([work_dir, "temp"]), cache=True, mutable=False
|
|
886
|
+
)
|
|
777
887
|
wantHardLink = True
|
|
778
888
|
else:
|
|
779
|
-
assert not job.fileStore.fileIsCached(
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
889
|
+
assert not job.fileStore.fileIsCached(
|
|
890
|
+
fsID
|
|
891
|
+
), "File mistakenly cached before read"
|
|
892
|
+
outfile = job.fileStore.readGlobalFile(
|
|
893
|
+
fsID, "/".join([work_dir, "temp"]), cache=False, mutable=False
|
|
894
|
+
)
|
|
895
|
+
assert not job.fileStore.fileIsCached(
|
|
896
|
+
fsID
|
|
897
|
+
), "File mistakenly cached after read"
|
|
783
898
|
wantHardLink = False
|
|
784
899
|
if isTest:
|
|
785
900
|
actual = os.stat(outfile).st_nlink
|
|
786
901
|
if wantHardLink:
|
|
787
|
-
assert actual > 1,
|
|
788
|
-
|
|
902
|
+
assert actual > 1, (
|
|
903
|
+
"Should have multiple links for file that was %s and %s. Got %i."
|
|
904
|
+
% (
|
|
905
|
+
"cached" if isCachedFile else "not cached",
|
|
906
|
+
"saved" if cacheReadFile else "not saved",
|
|
907
|
+
actual,
|
|
908
|
+
)
|
|
909
|
+
)
|
|
789
910
|
# We need to accept harf links even if we don't want them,
|
|
790
911
|
# because we may get them straight from the FileJobStore since
|
|
791
912
|
# we asked for immutable reads.
|
|
@@ -799,8 +920,12 @@ class hidden:
|
|
|
799
920
|
of links on the file are appropriate.
|
|
800
921
|
"""
|
|
801
922
|
A = Job.wrapJobFn(self._writeFileToJobStoreWithAsserts, isLocalFile=True)
|
|
802
|
-
B = Job.wrapJobFn(
|
|
803
|
-
|
|
923
|
+
B = Job.wrapJobFn(
|
|
924
|
+
self._readFromJobStore,
|
|
925
|
+
isCachedFile=True,
|
|
926
|
+
cacheReadFile=None,
|
|
927
|
+
fsID=A.rv(),
|
|
928
|
+
)
|
|
804
929
|
A.addChild(B)
|
|
805
930
|
Job.Runner.startToil(A, self.options)
|
|
806
931
|
|
|
@@ -830,23 +955,33 @@ class hidden:
|
|
|
830
955
|
|
|
831
956
|
:param bool cacheHit: Is the test for the CacheHit case(T) or cacheMiss case(F)
|
|
832
957
|
"""
|
|
833
|
-
dirPurpose =
|
|
958
|
+
dirPurpose = "tempWriteDir" if cacheHit else "nonLocalDir"
|
|
834
959
|
workdir = self._createTempDir(purpose=dirPurpose)
|
|
835
|
-
|
|
960
|
+
file_name = os.path.join(workdir, "test")
|
|
961
|
+
with open(file_name, "w") as x:
|
|
836
962
|
x.write(str(0))
|
|
837
|
-
A = Job.wrapJobFn(
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
963
|
+
A = Job.wrapJobFn(
|
|
964
|
+
self._writeFileToJobStoreWithAsserts,
|
|
965
|
+
isLocalFile=cacheHit,
|
|
966
|
+
nonLocalDir=workdir,
|
|
967
|
+
fileMB=256,
|
|
968
|
+
)
|
|
969
|
+
B = Job.wrapJobFn(self._probeJobReqs, sigmaJob=100, disk="100Mi")
|
|
841
970
|
jobs = {}
|
|
842
971
|
for i in range(0, 10):
|
|
843
|
-
jobs[i] = Job.wrapJobFn(
|
|
844
|
-
|
|
845
|
-
|
|
972
|
+
jobs[i] = Job.wrapJobFn(
|
|
973
|
+
self._multipleFileReader,
|
|
974
|
+
diskMB=1024,
|
|
975
|
+
fsID=A.rv(),
|
|
976
|
+
maxWriteFile=os.path.abspath(file_name),
|
|
977
|
+
disk="1Gi",
|
|
978
|
+
memory="10Mi",
|
|
979
|
+
cores=1,
|
|
980
|
+
)
|
|
846
981
|
A.addChild(jobs[i])
|
|
847
982
|
jobs[i].addChild(B)
|
|
848
983
|
Job.Runner.startToil(A, self.options)
|
|
849
|
-
with open(
|
|
984
|
+
with open(file_name) as y:
|
|
850
985
|
# At least one job at a time should have been observed.
|
|
851
986
|
# We can't actually guarantee that any of our jobs will
|
|
852
987
|
# see each other currently running.
|
|
@@ -867,29 +1002,35 @@ class hidden:
|
|
|
867
1002
|
file will be written
|
|
868
1003
|
"""
|
|
869
1004
|
work_dir = job.fileStore.getLocalTempDir()
|
|
870
|
-
outfile = job.fileStore.readGlobalFile(
|
|
871
|
-
|
|
1005
|
+
outfile = job.fileStore.readGlobalFile(
|
|
1006
|
+
fsID, "/".join([work_dir, "temp"]), cache=True, mutable=False
|
|
1007
|
+
)
|
|
872
1008
|
diskBytes = diskMB * 1024 * 1024
|
|
873
1009
|
fileStats = os.stat(outfile)
|
|
874
1010
|
fileSize = fileStats.st_size
|
|
875
1011
|
|
|
876
1012
|
currentReaders = job.fileStore.getFileReaderCount(fsID)
|
|
1013
|
+
# This should always count us
|
|
1014
|
+
assert currentReaders > 0
|
|
877
1015
|
|
|
878
1016
|
extraJobSpace = job.fileStore.getCacheExtraJobSpace()
|
|
879
1017
|
|
|
880
1018
|
usedCache = job.fileStore.getCacheUsed()
|
|
881
1019
|
|
|
882
|
-
logger.info(
|
|
883
|
-
logger.info(
|
|
884
|
-
logger.info(
|
|
885
|
-
logger.info(
|
|
886
|
-
logger.info(
|
|
1020
|
+
logger.info("Extra job space: %s", str(extraJobSpace))
|
|
1021
|
+
logger.info("Current file readers: %s", str(currentReaders))
|
|
1022
|
+
logger.info("File size: %s", str(fileSize))
|
|
1023
|
+
logger.info("Job disk bytes: %s", str(diskBytes))
|
|
1024
|
+
logger.info("Used cache: %s", str(usedCache))
|
|
887
1025
|
|
|
888
|
-
with open(maxWriteFile,
|
|
1026
|
+
with open(maxWriteFile, "r+") as x:
|
|
1027
|
+
# Advisory lock the file we are saving max readers to
|
|
1028
|
+
fcntl.lockf(x, fcntl.LOCK_EX)
|
|
889
1029
|
prev_max = int(x.read())
|
|
890
1030
|
x.seek(0)
|
|
891
1031
|
x.truncate()
|
|
892
1032
|
x.write(str(max(prev_max, currentReaders)))
|
|
1033
|
+
fcntl.lockf(x, fcntl.LOCK_UN)
|
|
893
1034
|
if job.fileStore.cachingIsFree():
|
|
894
1035
|
# No space should be used when caching is free
|
|
895
1036
|
assert usedCache == 0.0
|
|
@@ -898,23 +1039,26 @@ class hidden:
|
|
|
898
1039
|
assert usedCache == fileSize
|
|
899
1040
|
|
|
900
1041
|
# Make sure that there's no over-usage of job requirements
|
|
901
|
-
assert ((extraJobSpace + currentReaders * fileSize) %
|
|
902
|
-
diskBytes) == 0.0
|
|
1042
|
+
assert ((extraJobSpace + currentReaders * fileSize) % diskBytes) == 0.0
|
|
903
1043
|
# Sleep so there's no race conditions where a job ends before another can get a hold of
|
|
904
1044
|
# the file
|
|
905
1045
|
time.sleep(3)
|
|
906
1046
|
|
|
907
1047
|
@staticmethod
|
|
908
1048
|
def _writeExportGlobalFile(job):
|
|
909
|
-
fileName = os.path.join(job.fileStore.getLocalTempDir(),
|
|
910
|
-
with open(fileName,
|
|
911
|
-
f.write(os.urandom(1024 * 30000))
|
|
912
|
-
outputFile = os.path.join(job.fileStore.getLocalTempDir(),
|
|
913
|
-
job.fileStore.export_file(
|
|
1049
|
+
fileName = os.path.join(job.fileStore.getLocalTempDir(), "testfile")
|
|
1050
|
+
with open(fileName, "wb") as f:
|
|
1051
|
+
f.write(os.urandom(1024 * 30000)) # 30 Mb
|
|
1052
|
+
outputFile = os.path.join(job.fileStore.getLocalTempDir(), "exportedFile")
|
|
1053
|
+
job.fileStore.export_file(
|
|
1054
|
+
job.fileStore.writeGlobalFile(fileName), "File://" + outputFile
|
|
1055
|
+
)
|
|
914
1056
|
if not filecmp.cmp(fileName, outputFile):
|
|
915
|
-
logger.warning(
|
|
916
|
-
logger.warning(
|
|
917
|
-
raise RuntimeError(
|
|
1057
|
+
logger.warning("Source file: %s", str(os.stat(fileName)))
|
|
1058
|
+
logger.warning("Destination file: %s", str(os.stat(outputFile)))
|
|
1059
|
+
raise RuntimeError(
|
|
1060
|
+
f"File {fileName} did not properly get copied to {outputFile}"
|
|
1061
|
+
)
|
|
918
1062
|
|
|
919
1063
|
@slow
|
|
920
1064
|
def testFileStoreExportFile(self):
|
|
@@ -931,12 +1075,14 @@ class hidden:
|
|
|
931
1075
|
Read back written and locally deleted files. Ensure that after
|
|
932
1076
|
every step that the cache is in a valid state.
|
|
933
1077
|
"""
|
|
934
|
-
workdir = self._createTempDir(purpose=
|
|
935
|
-
F = Job.wrapJobFn(
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1078
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
1079
|
+
F = Job.wrapJobFn(
|
|
1080
|
+
self._returnFileTestFn,
|
|
1081
|
+
jobDisk=2 * 1024 * 1024 * 1024,
|
|
1082
|
+
initialCachedSize=0,
|
|
1083
|
+
nonLocalDir=workdir,
|
|
1084
|
+
disk="2Gi",
|
|
1085
|
+
)
|
|
940
1086
|
Job.Runner.startToil(F, self.options)
|
|
941
1087
|
|
|
942
1088
|
@slow
|
|
@@ -949,16 +1095,21 @@ class hidden:
|
|
|
949
1095
|
self.options.retryCount = 20
|
|
950
1096
|
self.options.badWorker = 0.5
|
|
951
1097
|
self.options.badWorkerFailInterval = 0.1
|
|
952
|
-
workdir = self._createTempDir(purpose=
|
|
953
|
-
F = Job.wrapJobFn(
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1098
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
1099
|
+
F = Job.wrapJobFn(
|
|
1100
|
+
self._returnFileTestFn,
|
|
1101
|
+
jobDisk=2 * 1024 * 1024 * 1024,
|
|
1102
|
+
initialCachedSize=0,
|
|
1103
|
+
nonLocalDir=workdir,
|
|
1104
|
+
numIters=30,
|
|
1105
|
+
disk="2Gi",
|
|
1106
|
+
)
|
|
958
1107
|
Job.Runner.startToil(F, self.options)
|
|
959
1108
|
|
|
960
1109
|
@staticmethod
|
|
961
|
-
def _returnFileTestFn(
|
|
1110
|
+
def _returnFileTestFn(
|
|
1111
|
+
job, jobDisk, initialCachedSize, nonLocalDir, numIters=100
|
|
1112
|
+
):
|
|
962
1113
|
"""
|
|
963
1114
|
Aux function for jobCacheTest.testReturnFileSizes Conduct numIters operations and ensure
|
|
964
1115
|
the cache has the right amount of data in it at all times.
|
|
@@ -970,7 +1121,7 @@ class hidden:
|
|
|
970
1121
|
:param float jobDisk: The value of disk passed to this job.
|
|
971
1122
|
"""
|
|
972
1123
|
cached = initialCachedSize
|
|
973
|
-
RealtimeLogger.info(
|
|
1124
|
+
RealtimeLogger.info("Expecting %d bytes cached initially", cached)
|
|
974
1125
|
work_dir = job.fileStore.getLocalTempDir()
|
|
975
1126
|
writtenFiles = {} # fsID: (size, isLocal)
|
|
976
1127
|
# fsid: local/mutable/immutable for all operations that should make local files as tracked by the FileStore
|
|
@@ -981,45 +1132,71 @@ class hidden:
|
|
|
981
1132
|
# We keep jobDisk in sync with the amount of free space the job
|
|
982
1133
|
# still has that the file store doesn't know it has used.
|
|
983
1134
|
cls = hidden.AbstractCachingFileStoreTest
|
|
984
|
-
fsId = cls._writeFileToJobStoreWithAsserts(
|
|
1135
|
+
fsId = cls._writeFileToJobStoreWithAsserts(
|
|
1136
|
+
job, isLocalFile=True, fileMB=writeFileSize
|
|
1137
|
+
)
|
|
985
1138
|
writtenFiles[fsId] = writeFileSize
|
|
986
1139
|
if job.fileStore.fileIsCached(list(writtenFiles.keys())[0]):
|
|
987
1140
|
cached += writeFileSize * 1024 * 1024
|
|
988
|
-
RealtimeLogger.info(
|
|
1141
|
+
RealtimeLogger.info(
|
|
1142
|
+
"Expecting %d bytes cached because file of %d MB is cached",
|
|
1143
|
+
cached,
|
|
1144
|
+
writeFileSize,
|
|
1145
|
+
)
|
|
989
1146
|
else:
|
|
990
|
-
RealtimeLogger.info(
|
|
991
|
-
|
|
992
|
-
|
|
1147
|
+
RealtimeLogger.info(
|
|
1148
|
+
"Expecting %d bytes cached because file of %d MB is not cached",
|
|
1149
|
+
cached,
|
|
1150
|
+
writeFileSize,
|
|
1151
|
+
)
|
|
1152
|
+
localFileIDs[list(writtenFiles.keys())[0]].append("local")
|
|
1153
|
+
RealtimeLogger.info("Checking for %d bytes cached", cached)
|
|
993
1154
|
cls._requirementsConcur(job, jobDisk, cached)
|
|
994
1155
|
i = 0
|
|
995
1156
|
while i <= numIters:
|
|
996
1157
|
randVal = random.random()
|
|
997
1158
|
if randVal < 0.33: # Write
|
|
998
|
-
RealtimeLogger.info(
|
|
1159
|
+
RealtimeLogger.info("Writing a file")
|
|
999
1160
|
writeFileSize = random.randint(0, 30)
|
|
1000
1161
|
if random.random() <= 0.5: # Write a local file
|
|
1001
|
-
RealtimeLogger.info(
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1162
|
+
RealtimeLogger.info(
|
|
1163
|
+
"Writing a local file of %d MB", writeFileSize
|
|
1164
|
+
)
|
|
1165
|
+
fsID = cls._writeFileToJobStoreWithAsserts(
|
|
1166
|
+
job, isLocalFile=True, fileMB=writeFileSize
|
|
1167
|
+
)
|
|
1168
|
+
RealtimeLogger.info("Wrote local file: %s", fsID)
|
|
1005
1169
|
writtenFiles[fsID] = writeFileSize
|
|
1006
|
-
localFileIDs[fsID].append(
|
|
1170
|
+
localFileIDs[fsID].append("local")
|
|
1007
1171
|
jobDisk -= writeFileSize * 1024 * 1024
|
|
1008
1172
|
if job.fileStore.fileIsCached(fsID):
|
|
1009
1173
|
cached += writeFileSize * 1024 * 1024
|
|
1010
|
-
RealtimeLogger.info(
|
|
1174
|
+
RealtimeLogger.info(
|
|
1175
|
+
"Expecting %d bytes cached because file of %d MB is cached",
|
|
1176
|
+
cached,
|
|
1177
|
+
writeFileSize,
|
|
1178
|
+
)
|
|
1011
1179
|
else:
|
|
1012
|
-
RealtimeLogger.info(
|
|
1180
|
+
RealtimeLogger.info(
|
|
1181
|
+
"Expecting %d bytes cached because file of %d MB is not cached",
|
|
1182
|
+
cached,
|
|
1183
|
+
writeFileSize,
|
|
1184
|
+
)
|
|
1013
1185
|
else: # Write a non-local file
|
|
1014
|
-
RealtimeLogger.info(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1186
|
+
RealtimeLogger.info(
|
|
1187
|
+
"Writing a non-local file of %d MB", writeFileSize
|
|
1188
|
+
)
|
|
1189
|
+
fsID = cls._writeFileToJobStoreWithAsserts(
|
|
1190
|
+
job,
|
|
1191
|
+
isLocalFile=False,
|
|
1192
|
+
nonLocalDir=nonLocalDir,
|
|
1193
|
+
fileMB=writeFileSize,
|
|
1194
|
+
)
|
|
1195
|
+
RealtimeLogger.info("Wrote non-local file: %s", fsID)
|
|
1019
1196
|
writtenFiles[fsID] = writeFileSize
|
|
1020
1197
|
# Don't record in localFileIDs because we're not local
|
|
1021
1198
|
# No change to the job since there was no caching
|
|
1022
|
-
RealtimeLogger.info(
|
|
1199
|
+
RealtimeLogger.info("Checking for %d bytes cached", cached)
|
|
1023
1200
|
cls._requirementsConcur(job, jobDisk, cached)
|
|
1024
1201
|
else:
|
|
1025
1202
|
if len(writtenFiles) == 0:
|
|
@@ -1029,61 +1206,101 @@ class hidden:
|
|
|
1029
1206
|
rdelRandVal = random.random()
|
|
1030
1207
|
fileWasCached = job.fileStore.fileIsCached(fsID)
|
|
1031
1208
|
if randVal < 0.66: # Read
|
|
1032
|
-
RealtimeLogger.info(
|
|
1209
|
+
RealtimeLogger.info(
|
|
1210
|
+
"Reading a file with size %d and previous cache status %s: %s",
|
|
1211
|
+
rdelFileSize,
|
|
1212
|
+
str(fileWasCached),
|
|
1213
|
+
fsID,
|
|
1214
|
+
)
|
|
1033
1215
|
if rdelRandVal <= 0.5: # Read as mutable, uncached
|
|
1034
|
-
RealtimeLogger.info(
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1216
|
+
RealtimeLogger.info(
|
|
1217
|
+
"Reading as mutable and uncached; should still have %d bytes cached",
|
|
1218
|
+
cached,
|
|
1219
|
+
)
|
|
1220
|
+
job.fileStore.readGlobalFile(
|
|
1221
|
+
fsID,
|
|
1222
|
+
"/".join([work_dir, str(uuid4())]),
|
|
1223
|
+
mutable=True,
|
|
1224
|
+
cache=False,
|
|
1225
|
+
)
|
|
1226
|
+
localFileIDs[fsID].append("mutable")
|
|
1038
1227
|
# No change because the file wasn't cached
|
|
1039
1228
|
else: # Read as immutable
|
|
1040
|
-
RealtimeLogger.info(
|
|
1041
|
-
job.fileStore.readGlobalFile(
|
|
1042
|
-
|
|
1043
|
-
|
|
1229
|
+
RealtimeLogger.info("Reading as immutable and cacheable")
|
|
1230
|
+
job.fileStore.readGlobalFile(
|
|
1231
|
+
fsID,
|
|
1232
|
+
"/".join([work_dir, str(uuid4())]),
|
|
1233
|
+
mutable=False,
|
|
1234
|
+
cache=True,
|
|
1235
|
+
)
|
|
1236
|
+
localFileIDs[fsID].append("immutable")
|
|
1044
1237
|
jobDisk -= rdelFileSize * 1024 * 1024
|
|
1045
1238
|
if not fileWasCached:
|
|
1046
1239
|
if job.fileStore.fileIsCached(fsID):
|
|
1047
|
-
RealtimeLogger.info(
|
|
1240
|
+
RealtimeLogger.info(
|
|
1241
|
+
"File was not cached before and is now. Should have %d bytes cached",
|
|
1242
|
+
cached,
|
|
1243
|
+
)
|
|
1048
1244
|
cached += rdelFileSize * 1024 * 1024
|
|
1049
1245
|
else:
|
|
1050
|
-
RealtimeLogger.info(
|
|
1051
|
-
|
|
1246
|
+
RealtimeLogger.info(
|
|
1247
|
+
"File was not cached before and still is not now. "
|
|
1248
|
+
"Should still have %d bytes cached",
|
|
1249
|
+
cached,
|
|
1250
|
+
)
|
|
1052
1251
|
else:
|
|
1053
|
-
RealtimeLogger.info(
|
|
1252
|
+
RealtimeLogger.info(
|
|
1253
|
+
"File was cached before. Should still have %d bytes cached",
|
|
1254
|
+
cached,
|
|
1255
|
+
)
|
|
1054
1256
|
cls._requirementsConcur(job, jobDisk, cached)
|
|
1055
1257
|
else: # Delete
|
|
1056
1258
|
if rdelRandVal <= 0.5: # Local Delete
|
|
1057
1259
|
if fsID not in list(localFileIDs.keys()):
|
|
1058
1260
|
continue
|
|
1059
|
-
RealtimeLogger.info(
|
|
1261
|
+
RealtimeLogger.info(
|
|
1262
|
+
"Deleting a file locally with history %s: %s",
|
|
1263
|
+
localFileIDs[fsID],
|
|
1264
|
+
fsID,
|
|
1265
|
+
)
|
|
1060
1266
|
job.fileStore.deleteLocalFile(fsID)
|
|
1061
1267
|
else: # Global Delete
|
|
1062
|
-
RealtimeLogger.info(
|
|
1268
|
+
RealtimeLogger.info("Deleting a file globally: %s", fsID)
|
|
1063
1269
|
job.fileStore.deleteGlobalFile(fsID)
|
|
1064
1270
|
try:
|
|
1065
1271
|
job.fileStore.readGlobalFile(fsID)
|
|
1066
1272
|
except FileNotFoundError as err:
|
|
1067
1273
|
pass
|
|
1068
1274
|
except:
|
|
1069
|
-
raise RuntimeError(
|
|
1275
|
+
raise RuntimeError(
|
|
1276
|
+
"Got wrong error type for read of deleted file"
|
|
1277
|
+
)
|
|
1070
1278
|
else:
|
|
1071
|
-
raise RuntimeError(
|
|
1279
|
+
raise RuntimeError("Able to read deleted file")
|
|
1072
1280
|
writtenFiles.pop(fsID)
|
|
1073
1281
|
if fsID in list(localFileIDs.keys()):
|
|
1074
1282
|
for lFID in localFileIDs[fsID]:
|
|
1075
|
-
if lFID !=
|
|
1283
|
+
if lFID != "mutable":
|
|
1076
1284
|
jobDisk += rdelFileSize * 1024 * 1024
|
|
1077
1285
|
localFileIDs.pop(fsID)
|
|
1078
1286
|
if fileWasCached:
|
|
1079
1287
|
if not job.fileStore.fileIsCached(fsID):
|
|
1080
1288
|
cached -= rdelFileSize * 1024 * 1024
|
|
1081
|
-
RealtimeLogger.info(
|
|
1289
|
+
RealtimeLogger.info(
|
|
1290
|
+
"File was cached before and is not now. Should have %d bytes cached",
|
|
1291
|
+
cached,
|
|
1292
|
+
)
|
|
1082
1293
|
else:
|
|
1083
|
-
RealtimeLogger.info(
|
|
1084
|
-
|
|
1294
|
+
RealtimeLogger.info(
|
|
1295
|
+
"File was cached before and still is cached now. "
|
|
1296
|
+
"Should still have %d bytes cached",
|
|
1297
|
+
cached,
|
|
1298
|
+
)
|
|
1085
1299
|
else:
|
|
1086
|
-
RealtimeLogger.info(
|
|
1300
|
+
RealtimeLogger.info(
|
|
1301
|
+
"File was not cached before deletion. Should still have %d bytes cached",
|
|
1302
|
+
cached,
|
|
1303
|
+
)
|
|
1087
1304
|
cls._requirementsConcur(job, jobDisk, cached)
|
|
1088
1305
|
i += 1
|
|
1089
1306
|
return jobDisk, cached
|
|
@@ -1098,15 +1315,32 @@ class hidden:
|
|
|
1098
1315
|
used = job.fileStore.getCacheUsed()
|
|
1099
1316
|
|
|
1100
1317
|
if not job.fileStore.cachingIsFree():
|
|
1101
|
-
RealtimeLogger.info(
|
|
1102
|
-
|
|
1318
|
+
RealtimeLogger.info(
|
|
1319
|
+
"Caching is not free; %d bytes are used and %d bytes are expected",
|
|
1320
|
+
used,
|
|
1321
|
+
cached,
|
|
1322
|
+
)
|
|
1323
|
+
assert used == cached, (
|
|
1324
|
+
"Cache should have %d bytes used, but actually has %d bytes used"
|
|
1325
|
+
% (cached, used)
|
|
1326
|
+
)
|
|
1103
1327
|
else:
|
|
1104
|
-
RealtimeLogger.info(
|
|
1105
|
-
|
|
1328
|
+
RealtimeLogger.info(
|
|
1329
|
+
"Caching is free; %d bytes are used and %d bytes would be expected if caching were not free",
|
|
1330
|
+
used,
|
|
1331
|
+
cached,
|
|
1332
|
+
)
|
|
1333
|
+
assert used == 0, (
|
|
1334
|
+
"Cache should have nothing in it, but actually has %d bytes used"
|
|
1335
|
+
% used
|
|
1336
|
+
)
|
|
1106
1337
|
|
|
1107
1338
|
jobUnused = job.fileStore.getCacheUnusedJobRequirement()
|
|
1108
1339
|
|
|
1109
|
-
assert jobUnused == jobDisk,
|
|
1340
|
+
assert jobUnused == jobDisk, (
|
|
1341
|
+
"Job should have %d bytes of disk for non-FileStore use but the FileStore reports %d"
|
|
1342
|
+
% (jobDisk, jobUnused)
|
|
1343
|
+
)
|
|
1110
1344
|
|
|
1111
1345
|
# Testing the resumability of a failed worker
|
|
1112
1346
|
@slow
|
|
@@ -1115,13 +1349,16 @@ class hidden:
|
|
|
1115
1349
|
Conduct a couple of job store operations. Then die. Ensure that the restarted job is
|
|
1116
1350
|
tracking values in the cache state file appropriately.
|
|
1117
1351
|
"""
|
|
1118
|
-
workdir = self._createTempDir(purpose=
|
|
1352
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
1119
1353
|
self.options.retryCount = 1
|
|
1120
1354
|
jobDiskBytes = 2 * 1024 * 1024 * 1024
|
|
1121
|
-
F = Job.wrapJobFn(
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1355
|
+
F = Job.wrapJobFn(
|
|
1356
|
+
self._controlledFailTestFn,
|
|
1357
|
+
jobDisk=jobDiskBytes,
|
|
1358
|
+
testDir=workdir,
|
|
1359
|
+
disk=jobDiskBytes,
|
|
1360
|
+
)
|
|
1361
|
+
G = Job.wrapJobFn(self._probeJobReqs, sigmaJob=100, disk="100Mi")
|
|
1125
1362
|
F.addChild(G)
|
|
1126
1363
|
Job.Runner.startToil(F, self.options)
|
|
1127
1364
|
|
|
@@ -1137,22 +1374,35 @@ class hidden:
|
|
|
1137
1374
|
"""
|
|
1138
1375
|
|
|
1139
1376
|
# Make sure we actually have the disk size we are supposed to
|
|
1140
|
-
job.fileStore.log_to_leader(
|
|
1141
|
-
|
|
1377
|
+
job.fileStore.log_to_leader(
|
|
1378
|
+
"Job is running with %d bytes of disk, %d requested"
|
|
1379
|
+
% (job.disk, jobDisk)
|
|
1380
|
+
)
|
|
1381
|
+
assert (
|
|
1382
|
+
job.disk == jobDisk
|
|
1383
|
+
), "Job was scheduled with %d bytes but requested %d" % (job.disk, jobDisk)
|
|
1142
1384
|
|
|
1143
1385
|
cls = hidden.AbstractCachingFileStoreTest
|
|
1144
|
-
if os.path.exists(os.path.join(testDir,
|
|
1145
|
-
with open(os.path.join(testDir,
|
|
1146
|
-
cached = unpack(
|
|
1147
|
-
RealtimeLogger.info(
|
|
1386
|
+
if os.path.exists(os.path.join(testDir, "testfile.test")):
|
|
1387
|
+
with open(os.path.join(testDir, "testfile.test"), "rb") as fH:
|
|
1388
|
+
cached = unpack("d", fH.read())[0]
|
|
1389
|
+
RealtimeLogger.info(
|
|
1390
|
+
"Loaded expected cache size of %d from testfile.test", cached
|
|
1391
|
+
)
|
|
1148
1392
|
cls._requirementsConcur(job, jobDisk, cached)
|
|
1149
1393
|
cls._returnFileTestFn(job, jobDisk, cached, testDir, 20)
|
|
1150
1394
|
else:
|
|
1151
|
-
RealtimeLogger.info(
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1395
|
+
RealtimeLogger.info(
|
|
1396
|
+
"Expecting cache size of 0 because testfile.test is absent"
|
|
1397
|
+
)
|
|
1398
|
+
modifiedJobReqs, cached = cls._returnFileTestFn(
|
|
1399
|
+
job, jobDisk, 0, testDir, 20
|
|
1400
|
+
)
|
|
1401
|
+
with open(os.path.join(testDir, "testfile.test"), "wb") as fH:
|
|
1402
|
+
fH.write(pack("d", cached))
|
|
1403
|
+
RealtimeLogger.info(
|
|
1404
|
+
"Wrote cache size of %d to testfile.test", cached
|
|
1405
|
+
)
|
|
1156
1406
|
os.kill(os.getpid(), signal.SIGKILL)
|
|
1157
1407
|
|
|
1158
1408
|
@slow
|
|
@@ -1171,9 +1421,15 @@ class hidden:
|
|
|
1171
1421
|
|
|
1172
1422
|
def _deleteLocallyReadFilesFn(self, readAsMutable):
|
|
1173
1423
|
self.options.retryCount = 0
|
|
1174
|
-
A = Job.wrapJobFn(
|
|
1175
|
-
|
|
1176
|
-
|
|
1424
|
+
A = Job.wrapJobFn(
|
|
1425
|
+
self._writeFileToJobStoreWithAsserts, isLocalFile=True, memory="10M"
|
|
1426
|
+
)
|
|
1427
|
+
B = Job.wrapJobFn(
|
|
1428
|
+
self._removeReadFileFn,
|
|
1429
|
+
A.rv(),
|
|
1430
|
+
readAsMutable=readAsMutable,
|
|
1431
|
+
memory="20M",
|
|
1432
|
+
)
|
|
1177
1433
|
A.addChild(B)
|
|
1178
1434
|
Job.Runner.startToil(A, self.options)
|
|
1179
1435
|
|
|
@@ -1191,9 +1447,10 @@ class hidden:
|
|
|
1191
1447
|
# Are we processing the read file or the written file?
|
|
1192
1448
|
processsingReadFile = True
|
|
1193
1449
|
# Read in the file
|
|
1194
|
-
outfile = job.fileStore.readGlobalFile(
|
|
1195
|
-
|
|
1196
|
-
|
|
1450
|
+
outfile = job.fileStore.readGlobalFile(
|
|
1451
|
+
fileToDelete, os.path.join(work_dir, "temp"), mutable=readAsMutable
|
|
1452
|
+
)
|
|
1453
|
+
tempfile = os.path.join(work_dir, "tmp.tmp")
|
|
1197
1454
|
# The first time we run this loop, processsingReadFile is True and fileToDelete is the
|
|
1198
1455
|
# file read from the job store. The second time, processsingReadFile is False and
|
|
1199
1456
|
# fileToDelete is one that was just written in to the job store. Ensure the correct
|
|
@@ -1203,7 +1460,9 @@ class hidden:
|
|
|
1203
1460
|
try:
|
|
1204
1461
|
job.fileStore.deleteLocalFile(fileToDelete)
|
|
1205
1462
|
except IllegalDeletionCacheError:
|
|
1206
|
-
job.fileStore.log_to_leader(
|
|
1463
|
+
job.fileStore.log_to_leader(
|
|
1464
|
+
"Detected a deleted file %s." % fileToDelete
|
|
1465
|
+
)
|
|
1207
1466
|
os.rename(tempfile, outfile)
|
|
1208
1467
|
else:
|
|
1209
1468
|
# If we are processing the write test, or if we are testing the immutably read
|
|
@@ -1212,7 +1471,7 @@ class hidden:
|
|
|
1212
1471
|
if processsingReadFile:
|
|
1213
1472
|
processsingReadFile = False
|
|
1214
1473
|
# Write a file
|
|
1215
|
-
with open(os.path.join(work_dir, str(uuid4())),
|
|
1474
|
+
with open(os.path.join(work_dir, str(uuid4())), "wb") as testFile:
|
|
1216
1475
|
testFile.write(os.urandom(1 * 1024 * 1024))
|
|
1217
1476
|
fileToDelete = job.fileStore.writeGlobalFile(testFile.name)
|
|
1218
1477
|
outfile = testFile.name
|
|
@@ -1224,7 +1483,7 @@ class hidden:
|
|
|
1224
1483
|
Test the deletion capabilities of deleteLocalFile
|
|
1225
1484
|
"""
|
|
1226
1485
|
self.options.retryCount = 0
|
|
1227
|
-
workdir = self._createTempDir(purpose=
|
|
1486
|
+
workdir = self._createTempDir(purpose="nonLocalDir")
|
|
1228
1487
|
A = Job.wrapJobFn(self._deleteLocalFileFn, nonLocalDir=workdir)
|
|
1229
1488
|
Job.Runner.startToil(A, self.options)
|
|
1230
1489
|
|
|
@@ -1236,11 +1495,11 @@ class hidden:
|
|
|
1236
1495
|
"""
|
|
1237
1496
|
work_dir = job.fileStore.getLocalTempDir()
|
|
1238
1497
|
# Write local file
|
|
1239
|
-
with open(os.path.join(work_dir, str(uuid4())),
|
|
1498
|
+
with open(os.path.join(work_dir, str(uuid4())), "wb") as localFile:
|
|
1240
1499
|
localFile.write(os.urandom(1 * 1024 * 1024))
|
|
1241
1500
|
localFsID = job.fileStore.writeGlobalFile(localFile.name)
|
|
1242
1501
|
# write Non-Local File
|
|
1243
|
-
with open(os.path.join(nonLocalDir, str(uuid4())),
|
|
1502
|
+
with open(os.path.join(nonLocalDir, str(uuid4())), "wb") as nonLocalFile:
|
|
1244
1503
|
nonLocalFile.write(os.urandom(1 * 1024 * 1024))
|
|
1245
1504
|
nonLocalFsID = job.fileStore.writeGlobalFile(nonLocalFile.name)
|
|
1246
1505
|
# Delete fsid of local file. The file should be deleted
|
|
@@ -1270,7 +1529,7 @@ class hidden:
|
|
|
1270
1529
|
assert not os.path.exists(readBackFile2)
|
|
1271
1530
|
# Try to get a non-FileID that doesn't exist.
|
|
1272
1531
|
try:
|
|
1273
|
-
job.fileStore.readGlobalFile(
|
|
1532
|
+
job.fileStore.readGlobalFile("bogus")
|
|
1274
1533
|
except NoSuchFileException:
|
|
1275
1534
|
# TODO: We would like to require TypeError, but for Cactus
|
|
1276
1535
|
# support we have to accept non-FileIDs.
|
|
@@ -1279,7 +1538,7 @@ class hidden:
|
|
|
1279
1538
|
raise RuntimeError("Managed to get a file from a non-FileID")
|
|
1280
1539
|
# Try to get a FileID for something that doesn't exist
|
|
1281
1540
|
try:
|
|
1282
|
-
job.fileStore.readGlobalFile(FileID(
|
|
1541
|
+
job.fileStore.readGlobalFile(FileID("bogus", 4096))
|
|
1283
1542
|
except NoSuchFileException:
|
|
1284
1543
|
pass
|
|
1285
1544
|
else:
|
|
@@ -1307,7 +1566,7 @@ class hidden:
|
|
|
1307
1566
|
Create and return a FileID for a non-cached file written via a stream.
|
|
1308
1567
|
"""
|
|
1309
1568
|
|
|
1310
|
-
messageBytes = b
|
|
1569
|
+
messageBytes = b"This is a test file\n"
|
|
1311
1570
|
|
|
1312
1571
|
with job.fileStore.jobStore.write_file_stream() as (out, idString):
|
|
1313
1572
|
# Write directly to the job store so the caching file store doesn't even see it.
|
|
@@ -1320,7 +1579,9 @@ class hidden:
|
|
|
1320
1579
|
return fileID
|
|
1321
1580
|
|
|
1322
1581
|
@staticmethod
|
|
1323
|
-
def _readFileWithDelay(
|
|
1582
|
+
def _readFileWithDelay(
|
|
1583
|
+
job, fileID, cores=0.1, memory=50 * 1024 * 1024, disk=50 * 1024 * 1024
|
|
1584
|
+
):
|
|
1324
1585
|
"""
|
|
1325
1586
|
Read a file from the CachingFileStore with a delay imposed on the download.
|
|
1326
1587
|
Should create contention.
|
|
@@ -1333,43 +1594,47 @@ class hidden:
|
|
|
1333
1594
|
job.fileStore.forceDownloadDelay = 120
|
|
1334
1595
|
|
|
1335
1596
|
readStart = datetime.datetime.now()
|
|
1336
|
-
logger.debug(
|
|
1597
|
+
logger.debug("Begin read at %s", str(readStart))
|
|
1337
1598
|
|
|
1338
1599
|
localPath = job.fileStore.readGlobalFile(fileID, cache=True, mutable=True)
|
|
1339
1600
|
|
|
1340
1601
|
readEnd = datetime.datetime.now()
|
|
1341
|
-
logger.debug(
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1602
|
+
logger.debug(
|
|
1603
|
+
"End read at %s: took %f seconds",
|
|
1604
|
+
str(readEnd),
|
|
1605
|
+
(readEnd - readStart).total_seconds(),
|
|
1606
|
+
)
|
|
1346
1607
|
|
|
1608
|
+
with open(localPath, "rb") as fh:
|
|
1609
|
+
text = fh.read().decode("utf-8").strip()
|
|
1610
|
+
logger.debug("Got file contents: %s", text)
|
|
1347
1611
|
|
|
1348
1612
|
|
|
1349
1613
|
class NonCachingFileStoreTestWithFileJobStore(hidden.AbstractNonCachingFileStoreTest):
|
|
1350
|
-
jobStoreType =
|
|
1614
|
+
jobStoreType = "file"
|
|
1615
|
+
|
|
1351
1616
|
|
|
1352
1617
|
@pytest.mark.timeout(1000)
|
|
1353
1618
|
class CachingFileStoreTestWithFileJobStore(hidden.AbstractCachingFileStoreTest):
|
|
1354
|
-
jobStoreType =
|
|
1619
|
+
jobStoreType = "file"
|
|
1355
1620
|
|
|
1356
1621
|
|
|
1357
1622
|
@needs_aws_ec2
|
|
1358
1623
|
class NonCachingFileStoreTestWithAwsJobStore(hidden.AbstractNonCachingFileStoreTest):
|
|
1359
|
-
jobStoreType =
|
|
1624
|
+
jobStoreType = "aws"
|
|
1360
1625
|
|
|
1361
1626
|
|
|
1362
1627
|
@slow
|
|
1363
1628
|
@needs_aws_ec2
|
|
1364
1629
|
@pytest.mark.timeout(1000)
|
|
1365
1630
|
class CachingFileStoreTestWithAwsJobStore(hidden.AbstractCachingFileStoreTest):
|
|
1366
|
-
jobStoreType =
|
|
1631
|
+
jobStoreType = "aws"
|
|
1367
1632
|
|
|
1368
1633
|
|
|
1369
1634
|
@needs_google_project
|
|
1370
1635
|
@needs_google_storage
|
|
1371
1636
|
class NonCachingFileStoreTestWithGoogleJobStore(hidden.AbstractNonCachingFileStoreTest):
|
|
1372
|
-
jobStoreType =
|
|
1637
|
+
jobStoreType = "google"
|
|
1373
1638
|
|
|
1374
1639
|
|
|
1375
1640
|
@slow
|
|
@@ -1377,7 +1642,7 @@ class NonCachingFileStoreTestWithGoogleJobStore(hidden.AbstractNonCachingFileSto
|
|
|
1377
1642
|
@needs_google_storage
|
|
1378
1643
|
@pytest.mark.timeout(1000)
|
|
1379
1644
|
class CachingFileStoreTestWithGoogleJobStore(hidden.AbstractCachingFileStoreTest):
|
|
1380
|
-
jobStoreType =
|
|
1645
|
+
jobStoreType = "google"
|
|
1381
1646
|
|
|
1382
1647
|
|
|
1383
1648
|
def _exportStaticMethodAsGlobalFunctions(cls):
|
|
@@ -1386,10 +1651,12 @@ def _exportStaticMethodAsGlobalFunctions(cls):
|
|
|
1386
1651
|
the convention that the first argument of a job function is named 'job'.
|
|
1387
1652
|
"""
|
|
1388
1653
|
for name, kind, clazz, value in inspect.classify_class_attrs(cls):
|
|
1389
|
-
if
|
|
1654
|
+
if (
|
|
1655
|
+
kind == "static method" and name != "__new__"
|
|
1656
|
+
): # __new__ became static in 3.7
|
|
1390
1657
|
method = value.__func__
|
|
1391
1658
|
args = inspect.getfullargspec(method).args
|
|
1392
|
-
if args and args[0] ==
|
|
1659
|
+
if args and args[0] == "job":
|
|
1393
1660
|
globals()[name] = method
|
|
1394
1661
|
|
|
1395
1662
|
|