toil 7.0.0__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.
Files changed (190) hide show
  1. toil/__init__.py +121 -83
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +137 -77
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +211 -101
  5. toil/batchSystems/awsBatch.py +237 -128
  6. toil/batchSystems/cleanup_support.py +22 -16
  7. toil/batchSystems/contained_executor.py +30 -26
  8. toil/batchSystems/gridengine.py +85 -49
  9. toil/batchSystems/htcondor.py +164 -87
  10. toil/batchSystems/kubernetes.py +622 -386
  11. toil/batchSystems/local_support.py +17 -12
  12. toil/batchSystems/lsf.py +132 -79
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +288 -149
  16. toil/batchSystems/mesos/executor.py +77 -49
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +38 -29
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +293 -123
  21. toil/batchSystems/slurm.py +489 -137
  22. toil/batchSystems/torque.py +46 -32
  23. toil/bus.py +141 -73
  24. toil/common.py +630 -359
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1114 -532
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +62 -41
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +88 -57
  32. toil/fileStores/cachingFileStore.py +711 -247
  33. toil/fileStores/nonCachingFileStore.py +113 -75
  34. toil/job.py +988 -315
  35. toil/jobStores/abstractJobStore.py +387 -243
  36. toil/jobStores/aws/jobStore.py +727 -403
  37. toil/jobStores/aws/utils.py +161 -109
  38. toil/jobStores/conftest.py +1 -0
  39. toil/jobStores/fileJobStore.py +289 -151
  40. toil/jobStores/googleJobStore.py +137 -70
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +614 -269
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +55 -28
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +193 -58
  49. toil/lib/aws/utils.py +238 -218
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +83 -49
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +322 -209
  55. toil/lib/ec2nodes.py +174 -106
  56. toil/lib/encryption/_dummy.py +5 -3
  57. toil/lib/encryption/_nacl.py +10 -6
  58. toil/lib/encryption/conftest.py +1 -0
  59. toil/lib/exceptions.py +26 -7
  60. toil/lib/expando.py +4 -2
  61. toil/lib/ftp_utils.py +217 -0
  62. toil/lib/generatedEC2Lists.py +127 -19
  63. toil/lib/humanize.py +6 -2
  64. toil/lib/integration.py +341 -0
  65. toil/lib/io.py +99 -11
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +65 -18
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +19 -7
  71. toil/lib/retry.py +115 -77
  72. toil/lib/threading.py +282 -80
  73. toil/lib/throttle.py +15 -14
  74. toil/options/common.py +834 -401
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +70 -19
  78. toil/provisioners/__init__.py +111 -46
  79. toil/provisioners/abstractProvisioner.py +322 -157
  80. toil/provisioners/aws/__init__.py +62 -30
  81. toil/provisioners/aws/awsProvisioner.py +980 -627
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +147 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +127 -61
  88. toil/server/celery_app.py +3 -1
  89. toil/server/cli/wes_cwl_runner.py +82 -53
  90. toil/server/utils.py +54 -28
  91. toil/server/wes/abstract_backend.py +64 -26
  92. toil/server/wes/amazon_wes_utils.py +21 -15
  93. toil/server/wes/tasks.py +121 -63
  94. toil/server/wes/toil_backend.py +142 -107
  95. toil/server/wsgi_app.py +4 -3
  96. toil/serviceManager.py +58 -22
  97. toil/statsAndLogging.py +148 -64
  98. toil/test/__init__.py +263 -179
  99. toil/test/batchSystems/batchSystemTest.py +438 -195
  100. toil/test/batchSystems/batch_system_plugin_test.py +18 -7
  101. toil/test/batchSystems/test_gridengine.py +173 -0
  102. toil/test/batchSystems/test_lsf_helper.py +67 -58
  103. toil/test/batchSystems/test_slurm.py +93 -47
  104. toil/test/cactus/test_cactus_integration.py +20 -22
  105. toil/test/cwl/cwlTest.py +271 -71
  106. toil/test/cwl/measure_default_memory.cwl +12 -0
  107. toil/test/cwl/not_run_required_input.cwl +29 -0
  108. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  109. toil/test/docs/scriptsTest.py +60 -34
  110. toil/test/jobStores/jobStoreTest.py +412 -235
  111. toil/test/lib/aws/test_iam.py +116 -48
  112. toil/test/lib/aws/test_s3.py +16 -9
  113. toil/test/lib/aws/test_utils.py +5 -6
  114. toil/test/lib/dockerTest.py +118 -141
  115. toil/test/lib/test_conversions.py +113 -115
  116. toil/test/lib/test_ec2.py +57 -49
  117. toil/test/lib/test_integration.py +104 -0
  118. toil/test/lib/test_misc.py +12 -5
  119. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  120. toil/test/mesos/helloWorld.py +7 -6
  121. toil/test/mesos/stress.py +25 -20
  122. toil/test/options/options.py +7 -2
  123. toil/test/provisioners/aws/awsProvisionerTest.py +293 -140
  124. toil/test/provisioners/clusterScalerTest.py +440 -250
  125. toil/test/provisioners/clusterTest.py +81 -42
  126. toil/test/provisioners/gceProvisionerTest.py +174 -100
  127. toil/test/provisioners/provisionerTest.py +25 -13
  128. toil/test/provisioners/restartScript.py +5 -4
  129. toil/test/server/serverTest.py +188 -141
  130. toil/test/sort/restart_sort.py +137 -68
  131. toil/test/sort/sort.py +134 -66
  132. toil/test/sort/sortTest.py +91 -49
  133. toil/test/src/autoDeploymentTest.py +140 -100
  134. toil/test/src/busTest.py +20 -18
  135. toil/test/src/checkpointTest.py +8 -2
  136. toil/test/src/deferredFunctionTest.py +49 -35
  137. toil/test/src/dockerCheckTest.py +33 -26
  138. toil/test/src/environmentTest.py +20 -10
  139. toil/test/src/fileStoreTest.py +538 -271
  140. toil/test/src/helloWorldTest.py +7 -4
  141. toil/test/src/importExportFileTest.py +61 -31
  142. toil/test/src/jobDescriptionTest.py +32 -17
  143. toil/test/src/jobEncapsulationTest.py +2 -0
  144. toil/test/src/jobFileStoreTest.py +74 -50
  145. toil/test/src/jobServiceTest.py +187 -73
  146. toil/test/src/jobTest.py +120 -70
  147. toil/test/src/miscTests.py +19 -18
  148. toil/test/src/promisedRequirementTest.py +82 -36
  149. toil/test/src/promisesTest.py +7 -6
  150. toil/test/src/realtimeLoggerTest.py +6 -6
  151. toil/test/src/regularLogTest.py +71 -37
  152. toil/test/src/resourceTest.py +80 -49
  153. toil/test/src/restartDAGTest.py +36 -22
  154. toil/test/src/resumabilityTest.py +9 -2
  155. toil/test/src/retainTempDirTest.py +45 -14
  156. toil/test/src/systemTest.py +12 -8
  157. toil/test/src/threadingTest.py +44 -25
  158. toil/test/src/toilContextManagerTest.py +10 -7
  159. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  160. toil/test/src/workerTest.py +33 -16
  161. toil/test/utils/toilDebugTest.py +70 -58
  162. toil/test/utils/toilKillTest.py +4 -5
  163. toil/test/utils/utilsTest.py +239 -102
  164. toil/test/wdl/wdltoil_test.py +789 -148
  165. toil/test/wdl/wdltoil_test_kubernetes.py +37 -23
  166. toil/toilState.py +52 -26
  167. toil/utils/toilConfig.py +13 -4
  168. toil/utils/toilDebugFile.py +44 -27
  169. toil/utils/toilDebugJob.py +85 -25
  170. toil/utils/toilDestroyCluster.py +11 -6
  171. toil/utils/toilKill.py +8 -3
  172. toil/utils/toilLaunchCluster.py +251 -145
  173. toil/utils/toilMain.py +37 -16
  174. toil/utils/toilRsyncCluster.py +27 -14
  175. toil/utils/toilSshCluster.py +45 -22
  176. toil/utils/toilStats.py +75 -36
  177. toil/utils/toilStatus.py +226 -119
  178. toil/utils/toilUpdateEC2Instances.py +3 -1
  179. toil/version.py +11 -11
  180. toil/wdl/utils.py +5 -5
  181. toil/wdl/wdltoil.py +3513 -1052
  182. toil/worker.py +269 -128
  183. toil-8.0.0.dist-info/METADATA +173 -0
  184. toil-8.0.0.dist-info/RECORD +253 -0
  185. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  186. toil-7.0.0.dist-info/METADATA +0 -158
  187. toil-7.0.0.dist-info/RECORD +0 -244
  188. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/LICENSE +0 -0
  189. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  190. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/provisioners/node.py CHANGED
@@ -13,12 +13,12 @@
13
13
  # limitations under the License.
14
14
  import datetime
15
15
  import logging
16
- import pipes
17
16
  import socket
18
17
  import subprocess
19
18
  import time
20
19
  from itertools import count
21
- from typing import Union, Dict, Optional, List, Any
20
+ from shlex import quote
21
+ from typing import Any, Optional, Union
22
22
 
23
23
  from toil.lib.memoize import parse_iso_utc
24
24
 
@@ -30,12 +30,21 @@ logger = logging.getLogger(__name__)
30
30
  class Node:
31
31
  maxWaitTime = 7 * 60
32
32
 
33
- def __init__(self, publicIP: str, privateIP: str, name: str, launchTime: Union[datetime.datetime, str],
34
- nodeType: Optional[str], preemptible: bool, tags: Optional[Dict[str, str]] = None, use_private_ip: Optional[bool] = None) -> None:
33
+ def __init__(
34
+ self,
35
+ publicIP: str,
36
+ privateIP: str,
37
+ name: str,
38
+ launchTime: Union[datetime.datetime, str],
39
+ nodeType: Optional[str],
40
+ preemptible: bool,
41
+ tags: Optional[dict[str, str]] = None,
42
+ use_private_ip: Optional[bool] = None,
43
+ ) -> None:
35
44
  self.publicIP = publicIP
36
45
  self.privateIP = privateIP
37
46
  if use_private_ip:
38
- self.effectiveIP = self.privateIP #or self.publicIP?
47
+ self.effectiveIP = self.privateIP # or self.publicIP?
39
48
  else:
40
49
  self.effectiveIP = self.publicIP or self.privateIP
41
50
  self.name = name
@@ -78,7 +87,7 @@ class Node:
78
87
  else:
79
88
  return 1
80
89
 
81
- def waitForNode(self, role: str, keyName: str='core') -> None:
90
+ def waitForNode(self, role: str, keyName: str = "core") -> None:
82
91
  self._waitForSSHPort()
83
92
  # wait here so docker commands can be used reliably afterwards
84
93
  self._waitForSSHKeys(keyName=keyName)
@@ -86,8 +95,8 @@ class Node:
86
95
  self._waitForAppliance(role=role, keyName=keyName)
87
96
 
88
97
  def copySshKeys(self, keyName):
89
- """ Copy authorized_keys file to the core user from the keyName user."""
90
- if keyName == 'core':
98
+ """Copy authorized_keys file to the core user from the keyName user."""
99
+ if keyName == "core":
91
100
  return # No point.
92
101
 
93
102
  # Make sure that keys are there.
@@ -96,9 +105,17 @@ class Node:
96
105
  # copy keys to core user so that the ssh calls will work
97
106
  # - normal mechanism failed unless public key was in the google-ssh format
98
107
  # - even so, the key wasn't copied correctly to the core account
99
- keyFile = '/home/%s/.ssh/authorized_keys' % keyName
100
- self.sshInstance('/usr/bin/sudo', '/usr/bin/cp', keyFile, '/home/core/.ssh', user=keyName)
101
- self.sshInstance('/usr/bin/sudo', '/usr/bin/chown', 'core', '/home/core/.ssh/authorized_keys', user=keyName)
108
+ keyFile = "/home/%s/.ssh/authorized_keys" % keyName
109
+ self.sshInstance(
110
+ "/usr/bin/sudo", "/usr/bin/cp", keyFile, "/home/core/.ssh", user=keyName
111
+ )
112
+ self.sshInstance(
113
+ "/usr/bin/sudo",
114
+ "/usr/bin/chown",
115
+ "core",
116
+ "/home/core/.ssh/authorized_keys",
117
+ user=keyName,
118
+ )
102
119
 
103
120
  def injectFile(self, fromFile, toFile, role):
104
121
  """
@@ -110,9 +127,13 @@ class Node:
110
127
  self.coreRsync([fromFile, ":" + toFile], applianceName=role)
111
128
  return True
112
129
  except Exception as e:
113
- logger.debug("Rsync to new node failed, trying again. Error message: %s" % e)
130
+ logger.debug(
131
+ "Rsync to new node failed, trying again. Error message: %s" % e
132
+ )
114
133
  time.sleep(10 * retry)
115
- raise RuntimeError(f"Failed to inject file {fromFile} to {role} with ip {self.effectiveIP}")
134
+ raise RuntimeError(
135
+ f"Failed to inject file {fromFile} to {role} with ip {self.effectiveIP}"
136
+ )
116
137
 
117
138
  def extractFile(self, fromFile, toFile, role):
118
139
  """
@@ -124,74 +145,111 @@ class Node:
124
145
  self.coreRsync([":" + fromFile, toFile], applianceName=role)
125
146
  return True
126
147
  except Exception as e:
127
- logger.debug("Rsync from new node failed, trying again. Error message: %s" % e)
148
+ logger.debug(
149
+ "Rsync from new node failed, trying again. Error message: %s" % e
150
+ )
128
151
  time.sleep(10 * retry)
129
- raise RuntimeError(f"Failed to extract file {fromFile} from {role} with ip {self.effectiveIP}")
152
+ raise RuntimeError(
153
+ f"Failed to extract file {fromFile} from {role} with ip {self.effectiveIP}"
154
+ )
130
155
 
131
- def _waitForSSHKeys(self, keyName='core'):
156
+ def _waitForSSHKeys(self, keyName="core"):
132
157
  # the propagation of public ssh keys vs. opening the SSH port is racey, so this method blocks until
133
158
  # the keys are propagated and the instance can be SSH into
134
159
  start_time = time.time()
135
160
  last_error = None
136
161
  while True:
137
162
  if time.time() - start_time > self.maxWaitTime:
138
- raise RuntimeError(f"Key propagation failed on machine with ip {self.effectiveIP}." +
139
- ("\n\nMake sure that your public key is attached to your account and you are using "
140
- "the correct private key. If you are using a key with a passphrase, be sure to "
141
- "set up ssh-agent. For details, refer to "
142
- "https://toil.readthedocs.io/en/latest/running/cloud/cloud.html."
143
- if last_error and 'Permission denied' in last_error else ""))
163
+ raise RuntimeError(
164
+ f"Key propagation failed on machine with ip {self.effectiveIP}."
165
+ + (
166
+ "\n\nMake sure that your public key is attached to your account and you are using "
167
+ "the correct private key. If you are using a key with a passphrase, be sure to "
168
+ "set up ssh-agent. For details, refer to "
169
+ "https://toil.readthedocs.io/en/latest/running/cloud/cloud.html."
170
+ if last_error and "Permission denied" in last_error
171
+ else ""
172
+ )
173
+ )
144
174
  try:
145
- logger.info('Attempting to establish SSH connection to %s@%s...', keyName, self.effectiveIP)
146
- self.sshInstance('ps', sshOptions=['-oBatchMode=yes'], user=keyName)
175
+ logger.info(
176
+ "Attempting to establish SSH connection to %s@%s...",
177
+ keyName,
178
+ self.effectiveIP,
179
+ )
180
+ self.sshInstance("ps", sshOptions=["-oBatchMode=yes"], user=keyName)
147
181
  except RuntimeError as err:
148
182
  last_error = str(err)
149
- logger.info('Connection rejected, waiting for public SSH key to be propagated. Trying again in 10s.')
183
+ logger.info(
184
+ "Connection rejected, waiting for public SSH key to be propagated. Trying again in 10s."
185
+ )
150
186
  time.sleep(10)
151
187
  else:
152
- logger.info('...SSH connection established.')
188
+ logger.info("...SSH connection established.")
153
189
  return
154
190
 
155
- def _waitForDockerDaemon(self, keyName='core'):
156
- logger.info('Waiting for docker on %s to start...', self.effectiveIP)
191
+ def _waitForDockerDaemon(self, keyName="core"):
192
+ logger.info("Waiting for docker on %s to start...", self.effectiveIP)
157
193
  sleepTime = 10
158
194
  startTime = time.time()
159
195
  while True:
160
196
  if time.time() - startTime > self.maxWaitTime:
161
- raise RuntimeError("Docker daemon failed to start on machine with ip %s" % self.effectiveIP)
197
+ raise RuntimeError(
198
+ "Docker daemon failed to start on machine with ip %s"
199
+ % self.effectiveIP
200
+ )
162
201
  try:
163
- output = self.sshInstance('/usr/bin/ps', 'auxww', sshOptions=['-oBatchMode=yes'], user=keyName)
164
- if b'dockerd' in output:
202
+ output = self.sshInstance(
203
+ "/usr/bin/ps", "auxww", sshOptions=["-oBatchMode=yes"], user=keyName
204
+ )
205
+ if b"dockerd" in output:
165
206
  # docker daemon has started
166
- logger.info('Docker daemon running')
207
+ logger.info("Docker daemon running")
167
208
  break
168
209
  else:
169
- logger.info('... Still waiting for docker daemon, trying in %s sec...' % sleepTime)
210
+ logger.info(
211
+ "... Still waiting for docker daemon, trying in %s sec..."
212
+ % sleepTime
213
+ )
170
214
  time.sleep(sleepTime)
171
215
  except RuntimeError:
172
216
  logger.info("Wait for docker daemon failed ssh, trying again.")
173
217
  sleepTime += 20
174
218
 
175
- def _waitForAppliance(self, role, keyName='core'):
176
- logger.info('Waiting for %s Toil appliance to start...', role)
219
+ def _waitForAppliance(self, role, keyName="core"):
220
+ logger.info("Waiting for %s Toil appliance to start...", role)
177
221
  sleepTime = 20
178
222
  startTime = time.time()
179
223
  while True:
180
224
  if time.time() - startTime > self.maxWaitTime:
181
- raise RuntimeError("Appliance failed to start on machine with IP: " + self.effectiveIP +
182
- "\nCheck if TOIL_APPLIANCE_SELF is set correctly and the container exists.")
225
+ raise RuntimeError(
226
+ "Appliance failed to start on machine with IP: "
227
+ + self.effectiveIP
228
+ + "\nCheck if TOIL_APPLIANCE_SELF is set correctly and the container exists."
229
+ )
183
230
  try:
184
- output = self.sshInstance('/usr/bin/docker', 'ps', sshOptions=['-oBatchMode=yes'], user=keyName)
185
-
186
- role = bytes(role, encoding='utf-8') if type(role) != type(output) else role
231
+ output = self.sshInstance(
232
+ "/usr/bin/docker",
233
+ "ps",
234
+ sshOptions=["-oBatchMode=yes"],
235
+ user=keyName,
236
+ )
237
+
238
+ role = (
239
+ bytes(role, encoding="utf-8")
240
+ if type(role) != type(output)
241
+ else role
242
+ )
187
243
 
188
244
  if role in output:
189
- logger.info('...Toil appliance started')
245
+ logger.info("...Toil appliance started")
190
246
  break
191
247
  else:
192
- logger.info('...Still waiting for appliance, trying again in %s sec...' % sleepTime)
193
- logger.debug(f'Role: {role}\n'
194
- f'Output: {output}\n\n')
248
+ logger.info(
249
+ "...Still waiting for appliance, trying again in %s sec..."
250
+ % sleepTime
251
+ )
252
+ logger.debug(f"Role: {role}\n" f"Output: {output}\n\n")
195
253
  time.sleep(sleepTime)
196
254
  except RuntimeError:
197
255
  # ignore exceptions, keep trying
@@ -205,13 +263,13 @@ class Node:
205
263
  :return: the number of unsuccessful attempts to connect to the port before a the first
206
264
  success
207
265
  """
208
- logger.debug('Waiting for ssh port on %s to open...', self.effectiveIP)
266
+ logger.debug("Waiting for ssh port on %s to open...", self.effectiveIP)
209
267
  for i in count():
210
268
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
211
269
  try:
212
270
  s.settimeout(a_short_time)
213
271
  s.connect((self.effectiveIP, 22))
214
- logger.debug('...ssh port open')
272
+ logger.debug("...ssh port open")
215
273
  return i
216
274
  except OSError:
217
275
  pass
@@ -225,7 +283,7 @@ class Node:
225
283
  interactive SSHing. The default value is False. Input=string is passed as
226
284
  input to the Popen call.
227
285
  """
228
- kwargs['appliance'] = True
286
+ kwargs["appliance"] = True
229
287
  return self.coreSSH(*args, **kwargs)
230
288
 
231
289
  def sshInstance(self, *args, **kwargs):
@@ -233,7 +291,7 @@ class Node:
233
291
  Run a command on the instance.
234
292
  Returns the binary output of the command.
235
293
  """
236
- kwargs['collectStdout'] = True
294
+ kwargs["collectStdout"] = True
237
295
  return self.coreSSH(*args, **kwargs)
238
296
 
239
297
  def coreSSH(self, *args, **kwargs):
@@ -249,64 +307,74 @@ class Node:
249
307
  :param bytes input: UTF-8 encoded input bytes to send to the command
250
308
 
251
309
  """
252
- commandTokens = ['ssh', '-tt']
253
- if not kwargs.pop('strict', False):
254
- kwargs['sshOptions'] = ['-oUserKnownHostsFile=/dev/null', '-oStrictHostKeyChecking=no'] + kwargs.get(
255
- 'sshOptions', [])
256
- sshOptions = kwargs.pop('sshOptions', None)
310
+ commandTokens = ["ssh", "-tt"]
311
+ if not kwargs.pop("strict", False):
312
+ kwargs["sshOptions"] = [
313
+ "-oUserKnownHostsFile=/dev/null",
314
+ "-oStrictHostKeyChecking=no",
315
+ ] + kwargs.get("sshOptions", [])
316
+ sshOptions = kwargs.pop("sshOptions", None)
257
317
  # Forward ports:
258
318
  # 5050 for Mesos dashboard (although to talk to agents you will need a proxy)
259
- commandTokens.extend(['-L', '5050:localhost:5050'])
319
+ commandTokens.extend(["-L", "5050:localhost:5050"])
260
320
  if sshOptions:
261
321
  # add specified options to ssh command
262
322
  assert isinstance(sshOptions, list)
263
323
  commandTokens.extend(sshOptions)
264
324
  # specify host
265
- user = kwargs.pop('user', 'core') # CHANGED: Is this needed?
266
- commandTokens.append(f'{user}@{str(self.effectiveIP)}')
325
+ user = kwargs.pop("user", "core") # CHANGED: Is this needed?
326
+ commandTokens.append(f"{user}@{str(self.effectiveIP)}")
267
327
 
268
- inputString = kwargs.pop('input', None)
328
+ inputString = kwargs.pop("input", None)
269
329
  if inputString is not None:
270
- kwargs['stdin'] = subprocess.PIPE
330
+ kwargs["stdin"] = subprocess.PIPE
271
331
 
272
- if kwargs.pop('collectStdout', None):
273
- kwargs['stdout'] = subprocess.PIPE
274
- kwargs['stderr'] = subprocess.PIPE
332
+ if kwargs.pop("collectStdout", None):
333
+ kwargs["stdout"] = subprocess.PIPE
334
+ kwargs["stderr"] = subprocess.PIPE
275
335
 
276
- tty = kwargs.pop('tty', None)
277
- if kwargs.pop('appliance', None):
278
- ttyFlag = '-t' if tty else ''
279
- commandTokens += ['docker', 'exec', '-i', ttyFlag, 'toil_leader']
336
+ tty = kwargs.pop("tty", None)
337
+ if kwargs.pop("appliance", None):
338
+ ttyFlag = "-t" if tty else ""
339
+ commandTokens += ["docker", "exec", "-i", ttyFlag, "toil_leader"]
280
340
 
281
- logger.debug('Node %s: %s', self.effectiveIP, ' '.join(args))
282
- args = list(map(pipes.quote, args))
341
+ logger.debug("Node %s: %s", self.effectiveIP, " ".join(args))
342
+ args = list(map(quote, args))
283
343
  commandTokens += args
284
- logger.debug('Full command %s', ' '.join(commandTokens))
344
+ logger.debug("Full command %s", " ".join(commandTokens))
285
345
  process = subprocess.Popen(commandTokens, **kwargs)
286
346
  stdout, stderr = process.communicate(input=inputString)
287
347
  # at this point the process has already exited, no need for a timeout
288
348
  exit_code = process.returncode
289
349
  # ssh has been throwing random 255 errors - why?
290
350
  if exit_code != 0:
291
- logger.info('Executing the command "%s" on the appliance returned a non-zero '
292
- 'exit code %s with stdout %s and stderr %s'
293
- % (' '.join(args), exit_code, stdout, stderr))
294
- raise RuntimeError('Executing the command "%s" on the appliance returned a non-zero '
295
- 'exit code %s with stdout %s and stderr %s'
296
- % (' '.join(args), exit_code, stdout, stderr))
351
+ logger.info(
352
+ 'Executing the command "%s" on the appliance returned a non-zero '
353
+ "exit code %s with stdout %s and stderr %s"
354
+ % (" ".join(args), exit_code, stdout, stderr)
355
+ )
356
+ raise RuntimeError(
357
+ 'Executing the command "%s" on the appliance returned a non-zero '
358
+ "exit code %s with stdout %s and stderr %s"
359
+ % (" ".join(args), exit_code, stdout, stderr)
360
+ )
297
361
  return stdout
298
362
 
299
- def coreRsync(self, args: List[str], applianceName: str = 'toil_leader', **kwargs: Any) -> int:
300
- remoteRsync = "docker exec -i %s rsync -v" % applianceName # Access rsync inside appliance
363
+ def coreRsync(
364
+ self, args: list[str], applianceName: str = "toil_leader", **kwargs: Any
365
+ ) -> int:
366
+ remoteRsync = (
367
+ "docker exec -i %s rsync -v" % applianceName
368
+ ) # Access rsync inside appliance
301
369
  parsedArgs = []
302
370
  sshCommand = "ssh"
303
- if not kwargs.pop('strict', False):
371
+ if not kwargs.pop("strict", False):
304
372
  sshCommand = "ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no"
305
373
  hostInserted = False
306
374
  # Insert remote host address
307
375
  for i in args:
308
376
  if i.startswith(":") and not hostInserted:
309
- user = kwargs.pop('user', 'core') # CHANGED: Is this needed?
377
+ user = kwargs.pop("user", "core") # CHANGED: Is this needed?
310
378
  i = (f"{user}@{self.effectiveIP}") + i
311
379
  hostInserted = True
312
380
  elif i.startswith(":") and hostInserted:
@@ -314,7 +382,7 @@ class Node:
314
382
  parsedArgs.append(i)
315
383
  if not hostInserted:
316
384
  raise ValueError("No remote host found in argument list")
317
- command = ['rsync', '-e', sshCommand, '--rsync-path', remoteRsync]
385
+ command = ["rsync", "-e", sshCommand, "--rsync-path", remoteRsync]
318
386
  logger.debug("Running %r.", command + parsedArgs)
319
387
 
320
388
  return subprocess.check_call(command + parsedArgs)
toil/realtimeLogger.py CHANGED
@@ -20,7 +20,7 @@ import os.path
20
20
  import socketserver as SocketServer
21
21
  import threading
22
22
  from types import TracebackType
23
- from typing import TYPE_CHECKING, Any, Optional, Type
23
+ from typing import TYPE_CHECKING, Any, Optional
24
24
 
25
25
  from toil.lib.misc import get_public_ip
26
26
  from toil.statsAndLogging import set_log_level
@@ -49,7 +49,7 @@ class LoggingDatagramHandler(SocketServer.BaseRequestHandler):
49
49
 
50
50
  try:
51
51
  # Parse it as JSON
52
- message_attrs = json.loads(data.decode('utf-8'))
52
+ message_attrs = json.loads(data.decode("utf-8"))
53
53
  # Fluff it up into a proper logging record
54
54
  record = logging.makeLogRecord(message_attrs)
55
55
  if isinstance(record.args, list):
@@ -81,7 +81,7 @@ class JSONDatagramHandler(logging.handlers.DatagramHandler):
81
81
 
82
82
  def makePickle(self, record: logging.LogRecord) -> bytes:
83
83
  """Actually, encode the record as bare JSON instead."""
84
- return json.dumps(record.__dict__).encode('utf-8')
84
+ return json.dumps(record.__dict__).encode("utf-8")
85
85
 
86
86
 
87
87
  class RealtimeLoggerMetaclass(type):
@@ -113,7 +113,7 @@ class RealtimeLogger(metaclass=RealtimeLoggerMetaclass):
113
113
  envPrefix = "TOIL_RT_LOGGING_"
114
114
 
115
115
  # Avoid duplicating the default level everywhere
116
- defaultLevel = 'INFO'
116
+ defaultLevel = "INFO"
117
117
 
118
118
  # State maintained on server and client
119
119
 
@@ -131,19 +131,24 @@ class RealtimeLogger(metaclass=RealtimeLoggerMetaclass):
131
131
  logger = None
132
132
 
133
133
  @classmethod
134
- def _startLeader(cls, batchSystem: 'AbstractBatchSystem', level: str = defaultLevel) -> None:
134
+ def _startLeader(
135
+ cls, batchSystem: "AbstractBatchSystem", level: str = defaultLevel
136
+ ) -> None:
135
137
  with cls.lock:
136
138
  if cls.initialized == 0:
137
139
  cls.initialized += 1
138
140
  if level:
139
- logger.info('Starting real-time logging.')
141
+ logger.info("Starting real-time logging.")
140
142
  # Start up the logging server
141
143
  cls.loggingServer = SocketServer.ThreadingUDPServer(
142
- server_address=('0.0.0.0', 0),
143
- RequestHandlerClass=LoggingDatagramHandler)
144
+ server_address=("0.0.0.0", 0),
145
+ RequestHandlerClass=LoggingDatagramHandler,
146
+ )
144
147
 
145
148
  # Set up a thread to do all the serving in the background and exit when we do
146
- cls.serverThread = threading.Thread(target=cls.loggingServer.serve_forever)
149
+ cls.serverThread = threading.Thread(
150
+ target=cls.loggingServer.serve_forever
151
+ )
147
152
  cls.serverThread.daemon = True
148
153
  cls.serverThread.start()
149
154
 
@@ -156,28 +161,30 @@ class RealtimeLogger(metaclass=RealtimeLoggerMetaclass):
156
161
  os.environ[name] = value
157
162
  batchSystem.setEnv(name)
158
163
 
159
- _setEnv('ADDRESS', '%s:%i' % (ip, port))
160
- _setEnv('LEVEL', level)
164
+ _setEnv("ADDRESS", "%s:%i" % (ip, port))
165
+ _setEnv("LEVEL", level)
161
166
  else:
162
- logger.debug('Real-time logging disabled')
167
+ logger.debug("Real-time logging disabled")
163
168
  else:
164
169
  if level:
165
- logger.warning('Ignoring nested request to start real-time logging')
170
+ logger.warning("Ignoring nested request to start real-time logging")
166
171
 
167
172
  @classmethod
168
173
  def _stopLeader(cls) -> None:
169
174
  """Stop the server on the leader."""
170
175
  with cls.lock:
171
176
  if cls.initialized == 0:
172
- raise RuntimeError("Can't stop the server on the leader as the leader was never initialized.")
177
+ raise RuntimeError(
178
+ "Can't stop the server on the leader as the leader was never initialized."
179
+ )
173
180
  cls.initialized -= 1
174
181
  if cls.initialized == 0:
175
182
  if cls.loggingServer:
176
- logger.info('Stopping real-time logging server.')
183
+ logger.info("Stopping real-time logging server.")
177
184
  cls.loggingServer.shutdown()
178
185
  cls.loggingServer = None
179
186
  if cls.serverThread:
180
- logger.info('Joining real-time logging server thread.')
187
+ logger.info("Joining real-time logging server thread.")
181
188
  cls.serverThread.join()
182
189
  cls.serverThread = None
183
190
  for k in list(os.environ.keys()):
@@ -198,9 +205,9 @@ class RealtimeLogger(metaclass=RealtimeLoggerMetaclass):
198
205
  if cls.logger is None:
199
206
  with cls.lock:
200
207
  if cls.logger is None:
201
- cls.logger = logging.getLogger('toil-rt')
208
+ cls.logger = logging.getLogger("toil-rt")
202
209
  try:
203
- level = os.environ[cls.envPrefix + 'LEVEL']
210
+ level = os.environ[cls.envPrefix + "LEVEL"]
204
211
  except KeyError:
205
212
  # There is no server running on the leader, so suppress most log messages
206
213
  # and skip the UDP stuff.
@@ -209,16 +216,16 @@ class RealtimeLogger(metaclass=RealtimeLoggerMetaclass):
209
216
  # Adopt the logging level set on the leader.
210
217
  set_log_level(level, cls.logger)
211
218
  try:
212
- address = os.environ[cls.envPrefix + 'ADDRESS']
219
+ address = os.environ[cls.envPrefix + "ADDRESS"]
213
220
  except KeyError:
214
221
  pass
215
222
  else:
216
223
  # We know where to send messages to, so send them.
217
- host, port = address.split(':')
224
+ host, port = address.split(":")
218
225
  cls.logger.addHandler(JSONDatagramHandler(host, int(port)))
219
226
  return cls.logger
220
227
 
221
- def __init__(self, batchSystem: 'AbstractBatchSystem', level: str = defaultLevel):
228
+ def __init__(self, batchSystem: "AbstractBatchSystem", level: str = defaultLevel):
222
229
  """
223
230
  Create a context manager that starts up the UDP server.
224
231
 
@@ -237,5 +244,10 @@ class RealtimeLogger(metaclass=RealtimeLoggerMetaclass):
237
244
  RealtimeLogger._startLeader(self.__batchSystem, level=self.__level)
238
245
 
239
246
  # noinspection PyUnusedLocal
240
- def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]) -> None:
247
+ def __exit__(
248
+ self,
249
+ exc_type: Optional[type[BaseException]],
250
+ exc_val: Optional[BaseException],
251
+ exc_tb: Optional[TracebackType],
252
+ ) -> None:
241
253
  RealtimeLogger._stopLeader()