toil 5.12.0__py3-none-any.whl → 6.1.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 +18 -13
- toil/batchSystems/abstractBatchSystem.py +39 -13
- toil/batchSystems/abstractGridEngineBatchSystem.py +24 -24
- toil/batchSystems/awsBatch.py +14 -14
- toil/batchSystems/cleanup_support.py +7 -3
- toil/batchSystems/contained_executor.py +3 -3
- toil/batchSystems/htcondor.py +0 -1
- toil/batchSystems/kubernetes.py +34 -31
- toil/batchSystems/local_support.py +3 -1
- toil/batchSystems/lsf.py +7 -7
- toil/batchSystems/mesos/batchSystem.py +7 -7
- toil/batchSystems/options.py +32 -83
- toil/batchSystems/registry.py +104 -23
- toil/batchSystems/singleMachine.py +16 -13
- toil/batchSystems/slurm.py +87 -16
- toil/batchSystems/torque.py +0 -1
- toil/bus.py +44 -8
- toil/common.py +544 -753
- toil/cwl/__init__.py +28 -32
- toil/cwl/cwltoil.py +595 -574
- toil/cwl/utils.py +55 -10
- toil/exceptions.py +1 -1
- toil/fileStores/__init__.py +2 -2
- toil/fileStores/abstractFileStore.py +88 -14
- toil/fileStores/cachingFileStore.py +610 -549
- toil/fileStores/nonCachingFileStore.py +46 -22
- toil/job.py +182 -101
- toil/jobStores/abstractJobStore.py +161 -95
- toil/jobStores/aws/jobStore.py +23 -9
- toil/jobStores/aws/utils.py +6 -6
- toil/jobStores/fileJobStore.py +116 -18
- toil/jobStores/googleJobStore.py +16 -7
- toil/jobStores/utils.py +5 -6
- toil/leader.py +87 -56
- toil/lib/accelerators.py +10 -5
- toil/lib/aws/__init__.py +3 -14
- toil/lib/aws/ami.py +22 -9
- toil/lib/aws/iam.py +21 -13
- toil/lib/aws/session.py +2 -16
- toil/lib/aws/utils.py +4 -5
- toil/lib/compatibility.py +1 -1
- toil/lib/conversions.py +26 -3
- toil/lib/docker.py +22 -23
- toil/lib/ec2.py +10 -6
- toil/lib/ec2nodes.py +106 -100
- toil/lib/encryption/_nacl.py +2 -1
- toil/lib/generatedEC2Lists.py +325 -18
- toil/lib/io.py +49 -2
- toil/lib/misc.py +1 -1
- toil/lib/resources.py +9 -2
- toil/lib/threading.py +101 -38
- toil/options/common.py +736 -0
- toil/options/cwl.py +336 -0
- toil/options/wdl.py +37 -0
- toil/provisioners/abstractProvisioner.py +9 -4
- toil/provisioners/aws/__init__.py +3 -6
- toil/provisioners/aws/awsProvisioner.py +6 -0
- toil/provisioners/clusterScaler.py +3 -2
- toil/provisioners/gceProvisioner.py +2 -2
- toil/realtimeLogger.py +2 -1
- toil/resource.py +24 -18
- toil/server/app.py +2 -3
- toil/server/cli/wes_cwl_runner.py +4 -4
- toil/server/utils.py +1 -1
- toil/server/wes/abstract_backend.py +3 -2
- toil/server/wes/amazon_wes_utils.py +5 -4
- toil/server/wes/tasks.py +2 -3
- toil/server/wes/toil_backend.py +2 -10
- toil/server/wsgi_app.py +2 -0
- toil/serviceManager.py +12 -10
- toil/statsAndLogging.py +41 -9
- toil/test/__init__.py +29 -54
- toil/test/batchSystems/batchSystemTest.py +11 -111
- toil/test/batchSystems/test_slurm.py +24 -8
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +58 -0
- toil/test/cwl/cwlTest.py +438 -223
- toil/test/cwl/glob_dir.cwl +15 -0
- toil/test/cwl/preemptible.cwl +21 -0
- toil/test/cwl/preemptible_expression.cwl +28 -0
- toil/test/cwl/revsort.cwl +1 -1
- toil/test/cwl/revsort2.cwl +1 -1
- toil/test/docs/scriptsTest.py +2 -3
- toil/test/jobStores/jobStoreTest.py +34 -21
- toil/test/lib/aws/test_iam.py +4 -14
- toil/test/lib/aws/test_utils.py +0 -3
- toil/test/lib/dockerTest.py +4 -4
- toil/test/lib/test_ec2.py +12 -17
- toil/test/mesos/helloWorld.py +4 -5
- toil/test/mesos/stress.py +1 -1
- toil/test/{wdl/conftest.py → options/__init__.py} +0 -10
- toil/test/options/options.py +37 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
- toil/test/provisioners/clusterScalerTest.py +6 -4
- toil/test/provisioners/clusterTest.py +23 -11
- toil/test/provisioners/gceProvisionerTest.py +0 -6
- toil/test/provisioners/restartScript.py +3 -2
- toil/test/server/serverTest.py +1 -1
- toil/test/sort/restart_sort.py +2 -1
- toil/test/sort/sort.py +2 -1
- toil/test/sort/sortTest.py +2 -13
- toil/test/src/autoDeploymentTest.py +45 -45
- toil/test/src/busTest.py +5 -5
- toil/test/src/checkpointTest.py +2 -2
- toil/test/src/deferredFunctionTest.py +1 -1
- toil/test/src/fileStoreTest.py +32 -16
- toil/test/src/helloWorldTest.py +1 -1
- toil/test/src/importExportFileTest.py +1 -1
- toil/test/src/jobDescriptionTest.py +2 -1
- toil/test/src/jobServiceTest.py +1 -1
- toil/test/src/jobTest.py +18 -18
- toil/test/src/miscTests.py +5 -3
- toil/test/src/promisedRequirementTest.py +3 -3
- toil/test/src/realtimeLoggerTest.py +1 -1
- toil/test/src/resourceTest.py +2 -2
- toil/test/src/restartDAGTest.py +1 -1
- toil/test/src/resumabilityTest.py +36 -2
- toil/test/src/retainTempDirTest.py +1 -1
- toil/test/src/systemTest.py +2 -2
- toil/test/src/toilContextManagerTest.py +2 -2
- toil/test/src/userDefinedJobArgTypeTest.py +1 -1
- toil/test/utils/toilDebugTest.py +98 -32
- toil/test/utils/toilKillTest.py +2 -2
- toil/test/utils/utilsTest.py +23 -3
- toil/test/wdl/wdltoil_test.py +223 -45
- toil/toilState.py +7 -6
- toil/utils/toilClean.py +1 -1
- toil/utils/toilConfig.py +36 -0
- toil/utils/toilDebugFile.py +60 -33
- toil/utils/toilDebugJob.py +39 -12
- toil/utils/toilDestroyCluster.py +1 -1
- toil/utils/toilKill.py +1 -1
- toil/utils/toilLaunchCluster.py +13 -2
- toil/utils/toilMain.py +3 -2
- toil/utils/toilRsyncCluster.py +1 -1
- toil/utils/toilSshCluster.py +1 -1
- toil/utils/toilStats.py +445 -305
- toil/utils/toilStatus.py +2 -5
- toil/version.py +10 -10
- toil/wdl/utils.py +2 -122
- toil/wdl/wdltoil.py +1257 -492
- toil/worker.py +55 -46
- toil-6.1.0.dist-info/METADATA +124 -0
- toil-6.1.0.dist-info/RECORD +241 -0
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/WHEEL +1 -1
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/entry_points.txt +0 -1
- toil/batchSystems/parasol.py +0 -379
- toil/batchSystems/tes.py +0 -459
- toil/test/batchSystems/parasolTestSupport.py +0 -117
- toil/test/wdl/builtinTest.py +0 -506
- toil/test/wdl/toilwdlTest.py +0 -522
- toil/wdl/toilwdl.py +0 -141
- toil/wdl/versions/dev.py +0 -107
- toil/wdl/versions/draft2.py +0 -980
- toil/wdl/versions/v1.py +0 -794
- toil/wdl/wdl_analysis.py +0 -116
- toil/wdl/wdl_functions.py +0 -997
- toil/wdl/wdl_synthesis.py +0 -1011
- toil/wdl/wdl_types.py +0 -243
- toil-5.12.0.dist-info/METADATA +0 -118
- toil-5.12.0.dist-info/RECORD +0 -244
- /toil/{wdl/versions → options}/__init__.py +0 -0
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/LICENSE +0 -0
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/top_level.txt +0 -0
toil/cwl/utils.py
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
18
|
import os
|
|
19
|
+
from pathlib import PurePosixPath
|
|
20
|
+
import posixpath
|
|
21
|
+
import stat
|
|
19
22
|
from typing import (
|
|
20
23
|
Any,
|
|
21
24
|
Callable,
|
|
@@ -24,7 +27,6 @@ from typing import (
|
|
|
24
27
|
List,
|
|
25
28
|
MutableMapping,
|
|
26
29
|
MutableSequence,
|
|
27
|
-
Tuple,
|
|
28
30
|
Type,
|
|
29
31
|
TypeVar,
|
|
30
32
|
Union,
|
|
@@ -32,6 +34,7 @@ from typing import (
|
|
|
32
34
|
|
|
33
35
|
from toil.fileStores import FileID
|
|
34
36
|
from toil.fileStores.abstractFileStore import AbstractFileStore
|
|
37
|
+
from toil.jobStores.abstractJobStore import AbstractJobStore
|
|
35
38
|
|
|
36
39
|
logger = logging.getLogger(__name__)
|
|
37
40
|
|
|
@@ -129,6 +132,32 @@ def visit_cwl_class_and_reduce(
|
|
|
129
132
|
|
|
130
133
|
DirectoryStructure = Dict[str, Union[str, "DirectoryStructure"]]
|
|
131
134
|
|
|
135
|
+
def get_from_structure(dir_dict: DirectoryStructure, path: str) -> Union[str, DirectoryStructure, None]:
|
|
136
|
+
"""
|
|
137
|
+
Given a relative path, follow it in the given directory structure.
|
|
138
|
+
|
|
139
|
+
Return the string URI for files, the directory dict for
|
|
140
|
+
subdirectories, or None for nonexistent things.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
# Resolve .. and split into path components
|
|
144
|
+
parts = PurePosixPath(posixpath.normpath(path)).parts
|
|
145
|
+
if len(parts) == 0:
|
|
146
|
+
return dir_dict
|
|
147
|
+
if parts[0] in ('..', '/'):
|
|
148
|
+
raise RuntimeError(f"Path {path} not resolvable in virtual directory")
|
|
149
|
+
found: Union[str, DirectoryStructure] = dir_dict
|
|
150
|
+
for part in parts:
|
|
151
|
+
# Go down by each path component in turn
|
|
152
|
+
if isinstance(found, str):
|
|
153
|
+
# Looking for a subdirectory of a file, which doesn't exist
|
|
154
|
+
return None
|
|
155
|
+
if part not in found:
|
|
156
|
+
return None
|
|
157
|
+
found = found[part]
|
|
158
|
+
# Now we're at the place we want to be.
|
|
159
|
+
return found
|
|
160
|
+
|
|
132
161
|
|
|
133
162
|
def download_structure(
|
|
134
163
|
file_store: AbstractFileStore,
|
|
@@ -140,17 +169,21 @@ def download_structure(
|
|
|
140
169
|
"""
|
|
141
170
|
Download nested dictionary from the Toil file store to a local path.
|
|
142
171
|
|
|
172
|
+
Guaranteed to fill the structure with real files, and not symlinks out of
|
|
173
|
+
it to elsewhere. File URIs may be toilfile: URIs or any other URI that
|
|
174
|
+
Toil's job store system can read.
|
|
175
|
+
|
|
143
176
|
:param file_store: The Toil file store to download from.
|
|
144
177
|
|
|
145
|
-
:param index: Maps from downloaded file path back to input
|
|
178
|
+
:param index: Maps from downloaded file path back to input URI.
|
|
146
179
|
|
|
147
180
|
:param existing: Maps from file_store_id URI to downloaded file path.
|
|
148
181
|
|
|
149
182
|
:param dir_dict: a dict from string to string (for files) or dict (for
|
|
150
|
-
|
|
183
|
+
subdirectories) describing a directory structure.
|
|
151
184
|
|
|
152
185
|
:param into_dir: The directory to download the top-level dict's files
|
|
153
|
-
|
|
186
|
+
into.
|
|
154
187
|
"""
|
|
155
188
|
logger.debug("Downloading directory with %s items", len(dir_dict))
|
|
156
189
|
|
|
@@ -167,14 +200,26 @@ def download_structure(
|
|
|
167
200
|
download_structure(file_store, index, existing, value, subdir)
|
|
168
201
|
else:
|
|
169
202
|
# This must be a file path uploaded to Toil.
|
|
170
|
-
|
|
171
|
-
|
|
203
|
+
if not isinstance(value, str):
|
|
204
|
+
raise RuntimeError(f"Did not find a file at {value}.")
|
|
205
|
+
|
|
172
206
|
logger.debug("Downloading contained file '%s'", name)
|
|
173
207
|
dest_path = os.path.join(into_dir, name)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
208
|
+
|
|
209
|
+
if value.startswith("toilfile:"):
|
|
210
|
+
# So download the file into place.
|
|
211
|
+
# Make sure to get a real copy of the file because we may need to
|
|
212
|
+
# mount the directory into a container as a whole.
|
|
213
|
+
file_store.readGlobalFile(
|
|
214
|
+
FileID.unpack(value[len("toilfile:") :]), dest_path, symlink=False
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
# We need to download from some other kind of URL.
|
|
218
|
+
size, executable = AbstractJobStore.read_from_url(value, open(dest_path, 'wb'))
|
|
219
|
+
if executable:
|
|
220
|
+
# Make the written file executable
|
|
221
|
+
os.chmod(dest_path, os.stat(dest_path).st_mode | stat.S_IXUSR)
|
|
222
|
+
|
|
178
223
|
# Update the index dicts
|
|
179
224
|
# TODO: why?
|
|
180
225
|
index[dest_path] = value
|
toil/exceptions.py
CHANGED
|
@@ -36,7 +36,7 @@ class FailedJobsException(Exception):
|
|
|
36
36
|
for job_desc in failed_jobs:
|
|
37
37
|
if job_desc.logJobStoreFileID:
|
|
38
38
|
with job_desc.getLogFileHandle(job_store) as f:
|
|
39
|
-
self.msg += "\n" + StatsAndLogging.formatLogStream(f, job_desc)
|
|
39
|
+
self.msg += "\n" + StatsAndLogging.formatLogStream(f, f'Log from job "{job_desc}"')
|
|
40
40
|
# catch failures to prepare more complex details and only return the basics
|
|
41
41
|
except Exception:
|
|
42
42
|
logger.exception("Exception when compiling information about failed jobs")
|
toil/fileStores/__init__.py
CHANGED
|
@@ -40,7 +40,7 @@ class FileID(str):
|
|
|
40
40
|
|
|
41
41
|
def pack(self) -> str:
|
|
42
42
|
"""Pack the FileID into a string so it can be passed through external code."""
|
|
43
|
-
return f'{self.size}:{
|
|
43
|
+
return f'{self.size}:{"1" if self.executable else "0"}:{self}'
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
46
|
def forPath(cls, fileStoreID: str, filePath: str) -> 'FileID':
|
|
@@ -54,7 +54,7 @@ class FileID(str):
|
|
|
54
54
|
vals = packedFileStoreID.split(':', 2)
|
|
55
55
|
# Break up the packed value
|
|
56
56
|
size = int(vals[0])
|
|
57
|
-
executable =
|
|
57
|
+
executable = (vals[1] == "1")
|
|
58
58
|
value = vals[2]
|
|
59
59
|
# Create the FileID
|
|
60
60
|
return cls(value, size, executable)
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import logging
|
|
15
15
|
import os
|
|
16
|
-
import tempfile
|
|
17
16
|
from abc import ABC, abstractmethod
|
|
18
17
|
from contextlib import contextmanager
|
|
18
|
+
from tempfile import mkstemp
|
|
19
19
|
from threading import Event, Semaphore
|
|
20
20
|
from typing import (IO,
|
|
21
21
|
TYPE_CHECKING,
|
|
@@ -26,21 +26,24 @@ from typing import (IO,
|
|
|
26
26
|
Generator,
|
|
27
27
|
Iterator,
|
|
28
28
|
List,
|
|
29
|
+
Literal,
|
|
29
30
|
Optional,
|
|
30
31
|
Set,
|
|
31
32
|
Tuple,
|
|
32
33
|
Type,
|
|
33
34
|
Union,
|
|
34
|
-
cast
|
|
35
|
+
cast,
|
|
36
|
+
overload)
|
|
35
37
|
|
|
36
38
|
import dill
|
|
37
39
|
|
|
38
|
-
from toil.common import Toil, cacheDirName
|
|
40
|
+
from toil.common import Toil, cacheDirName, getDirSizeRecursively
|
|
39
41
|
from toil.fileStores import FileID
|
|
40
42
|
from toil.job import Job, JobDescription
|
|
41
43
|
from toil.jobStores.abstractJobStore import AbstractJobStore
|
|
42
44
|
from toil.lib.compatibility import deprecated
|
|
43
|
-
from toil.lib.
|
|
45
|
+
from toil.lib.conversions import bytes2human
|
|
46
|
+
from toil.lib.io import WriteWatchingStream, mkdtemp
|
|
44
47
|
|
|
45
48
|
logger = logging.getLogger(__name__)
|
|
46
49
|
|
|
@@ -114,19 +117,18 @@ class AbstractFileStore(ABC):
|
|
|
114
117
|
self.jobDesc.command.split()[1] if self.jobDesc.command else ""
|
|
115
118
|
)
|
|
116
119
|
self.waitForPreviousCommit = waitForPreviousCommit
|
|
117
|
-
self.
|
|
120
|
+
self.logging_messages: List[Dict[str, Union[int, str]]] = []
|
|
121
|
+
self.logging_user_streams: List[dict[str, str]] = []
|
|
118
122
|
# Records file IDs of files deleted during the current job. Doesn't get
|
|
119
123
|
# committed back until the job is completely successful, because if the
|
|
120
124
|
# job is re-run it will need to be able to re-delete these files.
|
|
121
125
|
# This is a set of str objects, not FileIDs.
|
|
122
126
|
self.filesToDelete: Set[str] = set()
|
|
123
|
-
# Records IDs of jobs that need to be deleted when the currently
|
|
124
|
-
# running job is cleaned up.
|
|
125
|
-
# May be modified by the worker to actually delete jobs!
|
|
126
|
-
self.jobsToDelete: Set[str] = set()
|
|
127
127
|
# Holds records of file ID, or file ID and local path, for reporting
|
|
128
128
|
# the accessed files of failed jobs.
|
|
129
129
|
self._accessLog: List[Tuple[str, ...]] = []
|
|
130
|
+
# Holds total bytes of observed disk usage for the last job run under open()
|
|
131
|
+
self._job_disk_used: Optional[int] = None
|
|
130
132
|
|
|
131
133
|
@staticmethod
|
|
132
134
|
def createFileStore(
|
|
@@ -190,6 +192,7 @@ class AbstractFileStore(ABC):
|
|
|
190
192
|
:param job: The job instance of the toil job to run.
|
|
191
193
|
"""
|
|
192
194
|
failed = True
|
|
195
|
+
job_requested_disk = job.disk
|
|
193
196
|
try:
|
|
194
197
|
yield
|
|
195
198
|
failed = False
|
|
@@ -199,6 +202,33 @@ class AbstractFileStore(ABC):
|
|
|
199
202
|
if failed:
|
|
200
203
|
self._dumpAccessLogs()
|
|
201
204
|
|
|
205
|
+
# See how much disk space is used at the end of the job.
|
|
206
|
+
# Not a real peak disk usage, but close enough to be useful for warning the user.
|
|
207
|
+
self._job_disk_used = getDirSizeRecursively(self.localTempDir)
|
|
208
|
+
|
|
209
|
+
# Report disk usage
|
|
210
|
+
percent: float = 0.0
|
|
211
|
+
if job_requested_disk and job_requested_disk > 0:
|
|
212
|
+
percent = float(self._job_disk_used) / job_requested_disk * 100
|
|
213
|
+
disk_usage: str = (f"Job {self.jobName} used {percent:.2f}% disk ({bytes2human(self._job_disk_used)}B [{self._job_disk_used}B] used, "
|
|
214
|
+
f"{bytes2human(job_requested_disk)}B [{job_requested_disk}B] requested).")
|
|
215
|
+
if self._job_disk_used > job_requested_disk:
|
|
216
|
+
self.log_to_leader("Job used more disk than requested. For CWL, consider increasing the outdirMin "
|
|
217
|
+
f"requirement, otherwise, consider increasing the disk requirement. {disk_usage}",
|
|
218
|
+
level=logging.WARNING)
|
|
219
|
+
else:
|
|
220
|
+
self.log_to_leader(disk_usage, level=logging.DEBUG)
|
|
221
|
+
|
|
222
|
+
def get_disk_usage(self) -> Optional[int]:
|
|
223
|
+
"""
|
|
224
|
+
Get the number of bytes of disk used by the last job run under open().
|
|
225
|
+
|
|
226
|
+
Disk usage is measured at the end of the job.
|
|
227
|
+
TODO: Sample periodically and record peak usage.
|
|
228
|
+
"""
|
|
229
|
+
return self._job_disk_used
|
|
230
|
+
|
|
231
|
+
|
|
202
232
|
# Functions related to temp files and directories
|
|
203
233
|
def getLocalTempDir(self) -> str:
|
|
204
234
|
"""
|
|
@@ -211,7 +241,7 @@ class AbstractFileStore(ABC):
|
|
|
211
241
|
to be deleted once the job terminates, removing all files it
|
|
212
242
|
contains recursively.
|
|
213
243
|
"""
|
|
214
|
-
return os.path.abspath(
|
|
244
|
+
return os.path.abspath(mkdtemp(dir=self.localTempDir))
|
|
215
245
|
|
|
216
246
|
def getLocalTempFile(self, suffix: Optional[str] = None, prefix: Optional[str] = None) -> str:
|
|
217
247
|
"""
|
|
@@ -227,7 +257,7 @@ class AbstractFileStore(ABC):
|
|
|
227
257
|
for the duration of the job only, and is guaranteed to be deleted
|
|
228
258
|
once the job terminates.
|
|
229
259
|
"""
|
|
230
|
-
handle, tmpFile =
|
|
260
|
+
handle, tmpFile = mkstemp(
|
|
231
261
|
suffix=".tmp" if suffix is None else suffix,
|
|
232
262
|
prefix="tmp" if prefix is None else prefix,
|
|
233
263
|
dir=self.localTempDir
|
|
@@ -417,6 +447,21 @@ class AbstractFileStore(ABC):
|
|
|
417
447
|
"""
|
|
418
448
|
raise NotImplementedError()
|
|
419
449
|
|
|
450
|
+
@overload
|
|
451
|
+
def readGlobalFileStream(
|
|
452
|
+
self,
|
|
453
|
+
fileStoreID: str,
|
|
454
|
+
encoding: Literal[None] = None,
|
|
455
|
+
errors: Optional[str] = None,
|
|
456
|
+
) -> ContextManager[IO[bytes]]:
|
|
457
|
+
...
|
|
458
|
+
|
|
459
|
+
@overload
|
|
460
|
+
def readGlobalFileStream(
|
|
461
|
+
self, fileStoreID: str, encoding: str, errors: Optional[str] = None
|
|
462
|
+
) -> ContextManager[IO[str]]:
|
|
463
|
+
...
|
|
464
|
+
|
|
420
465
|
@abstractmethod
|
|
421
466
|
def readGlobalFileStream(
|
|
422
467
|
self,
|
|
@@ -589,7 +634,7 @@ class AbstractFileStore(ABC):
|
|
|
589
634
|
os.rename(fileName + '.tmp', fileName)
|
|
590
635
|
|
|
591
636
|
# Functions related to logging
|
|
592
|
-
def
|
|
637
|
+
def log_to_leader(self, text: str, level: int = logging.INFO) -> None:
|
|
593
638
|
"""
|
|
594
639
|
Send a logging message to the leader. The message will also be \
|
|
595
640
|
logged by the worker at the same level.
|
|
@@ -598,7 +643,30 @@ class AbstractFileStore(ABC):
|
|
|
598
643
|
:param level: The logging level.
|
|
599
644
|
"""
|
|
600
645
|
logger.log(level=level, msg=("LOG-TO-MASTER: " + text))
|
|
601
|
-
self.
|
|
646
|
+
self.logging_messages.append(dict(text=text, level=level))
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
@deprecated(new_function_name='export_file')
|
|
650
|
+
def logToMaster(self, text: str, level: int = logging.INFO) -> None:
|
|
651
|
+
self.log_to_leader(text, level)
|
|
652
|
+
|
|
653
|
+
def log_user_stream(self, name: str, stream: IO[bytes]) -> None:
|
|
654
|
+
"""
|
|
655
|
+
Send a stream of UTF-8 text to the leader as a named log stream.
|
|
656
|
+
|
|
657
|
+
Useful for things like the error logs of Docker containers. The leader
|
|
658
|
+
will show it to the user or organize it appropriately for user-level
|
|
659
|
+
log information.
|
|
660
|
+
|
|
661
|
+
:param name: A hierarchical, .-delimited string.
|
|
662
|
+
:param stream: A stream of encoded text. Encoding errors will be
|
|
663
|
+
tolerated.
|
|
664
|
+
"""
|
|
665
|
+
|
|
666
|
+
# Read the whole stream into memory
|
|
667
|
+
steam_data = stream.read().decode('utf-8', errors='replace')
|
|
668
|
+
# And remember it for the worker to fish out
|
|
669
|
+
self.logging_user_streams.append(dict(name=name, text=steam_data))
|
|
602
670
|
|
|
603
671
|
# Functions run after the completion of the job.
|
|
604
672
|
@abstractmethod
|
|
@@ -606,7 +674,13 @@ class AbstractFileStore(ABC):
|
|
|
606
674
|
"""
|
|
607
675
|
Update the status of the job on the disk.
|
|
608
676
|
|
|
609
|
-
May
|
|
677
|
+
May bump the version number of the job.
|
|
678
|
+
|
|
679
|
+
May start an asynchronous process. Call waitForCommit() to wait on that
|
|
680
|
+
process. You must waitForCommit() before committing any further updates
|
|
681
|
+
to the job. During the asynchronous process, it is safe to modify the
|
|
682
|
+
job; modifications after this call will not be committed until the next
|
|
683
|
+
call.
|
|
610
684
|
|
|
611
685
|
:param jobState: If True, commit the state of the FileStore's job,
|
|
612
686
|
and file deletes. Otherwise, commit only file creates/updates.
|