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
@@ -19,26 +19,37 @@ import time
19
19
  from abc import abstractmethod
20
20
  from inspect import getsource
21
21
  from textwrap import dedent
22
- from typing import Optional, List
22
+ from typing import TYPE_CHECKING, Optional
23
23
  from uuid import uuid4
24
24
 
25
- import botocore.exceptions
26
25
  import pytest
27
- from mypy_boto3_ec2 import EC2Client
28
- from mypy_boto3_ec2.type_defs import EbsInstanceBlockDeviceTypeDef, InstanceTypeDef, InstanceBlockDeviceMappingTypeDef, FilterTypeDef, DescribeVolumesResultTypeDef, VolumeTypeDef
29
26
 
30
27
  from toil.provisioners import cluster_factory
31
28
  from toil.provisioners.aws.awsProvisioner import AWSProvisioner
32
- from toil.test import (ToilTest,
33
- integrative,
34
- needs_aws_ec2,
35
- needs_fetchable_appliance,
36
- needs_mesos,
37
- slow,
38
- timeLimit)
29
+ from toil.test import (
30
+ ToilTest,
31
+ integrative,
32
+ needs_aws_ec2,
33
+ needs_fetchable_appliance,
34
+ needs_mesos,
35
+ slow,
36
+ timeLimit,
37
+ )
39
38
  from toil.test.provisioners.clusterTest import AbstractClusterTest
40
39
  from toil.version import exactPython
41
40
 
41
+ if TYPE_CHECKING:
42
+ from mypy_boto3_ec2 import EC2Client
43
+ from mypy_boto3_ec2.type_defs import (
44
+ DescribeVolumesResultTypeDef,
45
+ EbsInstanceBlockDeviceTypeDef,
46
+ FilterTypeDef,
47
+ InstanceBlockDeviceMappingTypeDef,
48
+ InstanceTypeDef,
49
+ VolumeTypeDef,
50
+ )
51
+
52
+
42
53
  log = logging.getLogger(__name__)
43
54
 
44
55
 
@@ -50,11 +61,13 @@ class AWSProvisionerBenchTest(ToilTest):
50
61
  # Needs to talk to EC2 for image discovery
51
62
  @needs_aws_ec2
52
63
  def test_AMI_finding(self):
53
- for zone in ['us-west-2a', 'eu-central-1a', 'sa-east-1b']:
54
- provisioner = AWSProvisioner('fakename', 'mesos', zone, 10000, None, None)
64
+ for zone in ["us-west-2a", "eu-central-1a", "sa-east-1b"]:
65
+ provisioner = AWSProvisioner(
66
+ "fakename", "mesos", zone, 10000, None, None, enable_fuse=False
67
+ )
55
68
  ami = provisioner._discoverAMI()
56
69
  # Make sure we got an AMI and it looks plausible
57
- assert(ami.startswith('ami-'))
70
+ assert ami.startswith("ami-")
58
71
 
59
72
  @needs_aws_ec2
60
73
  def test_read_write_global_files(self):
@@ -62,8 +75,16 @@ class AWSProvisionerBenchTest(ToilTest):
62
75
  Make sure the `_write_file_to_cloud()` and `_read_file_from_cloud()`
63
76
  functions of the AWS provisioner work as intended.
64
77
  """
65
- provisioner = AWSProvisioner(f'aws-provisioner-test-{uuid4()}', 'mesos', 'us-west-2a', 50, None, None)
66
- key = 'config/test.txt'
78
+ provisioner = AWSProvisioner(
79
+ f"aws-provisioner-test-{uuid4()}",
80
+ "mesos",
81
+ "us-west-2a",
82
+ 50,
83
+ None,
84
+ None,
85
+ enable_fuse=False,
86
+ )
87
+ key = "config/test.txt"
67
88
  contents = b"Hello, this is a test."
68
89
 
69
90
  try:
@@ -84,23 +105,23 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
84
105
  def __init__(self, methodName):
85
106
  super().__init__(methodName=methodName)
86
107
  self.instanceTypes = ["m5a.large"]
87
- self.clusterName = 'aws-provisioner-test-' + str(uuid4())
88
- self.numWorkers = ['2']
108
+ self.clusterName = "aws-provisioner-test-" + str(uuid4())
109
+ self.numWorkers = ["2"]
89
110
  self.numSamples = 2
90
111
  self.spotBid = 0.15
91
112
  # We can't dump our user script right in /tmp or /home, because hot
92
113
  # deploy refuses to zip up those whole directories. So we make sure to
93
114
  # have a subdirectory to upload the script to.
94
- self.scriptDir = '/tmp/t'
115
+ self.scriptDir = "/tmp/t"
95
116
  # Where should we put our virtualenv?
96
- self.venvDir = '/tmp/venv'
117
+ self.venvDir = "/tmp/venv"
97
118
  # Where should we put our data to work on?
98
119
  # Must exist in the Toil container; the leader will try to rsync to it
99
120
  # (for the SSE key) and not create it.
100
- self.dataDir = '/tmp'
121
+ self.dataDir = "/tmp"
101
122
  # What filename should we use for our script (without path)?
102
123
  # Can be changed by derived tests.
103
- self.scriptName = 'test_script.py'
124
+ self.scriptName = "test_script.py"
104
125
 
105
126
  def script(self):
106
127
  """
@@ -115,20 +136,35 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
115
136
  return os.path.join(self.dataDir, filename)
116
137
 
117
138
  def rsyncUtil(self, src, dest):
118
- subprocess.check_call(['toil', 'rsync-cluster', '--insecure', '-p=aws', '-z', self.zone, self.clusterName] + [src, dest])
139
+ subprocess.check_call(
140
+ [
141
+ "toil",
142
+ "rsync-cluster",
143
+ "--insecure",
144
+ "-p=aws",
145
+ "-z",
146
+ self.zone,
147
+ self.clusterName,
148
+ ]
149
+ + [src, dest]
150
+ )
119
151
 
120
152
  def getRootVolID(self) -> str:
121
- instances: List[InstanceTypeDef] = self.cluster._get_nodes_in_cluster_boto3()
153
+ instances: list["InstanceTypeDef"] = self.cluster._get_nodes_in_cluster_boto3()
122
154
  instances.sort(key=lambda x: x.get("LaunchTime"))
123
- leader: InstanceTypeDef = instances[0] # assume leader was launched first
155
+ leader: "InstanceTypeDef" = instances[0] # assume leader was launched first
124
156
 
125
- bdm: Optional[List[InstanceBlockDeviceMappingTypeDef]] = leader.get("BlockDeviceMappings")
157
+ bdm: Optional[list["InstanceBlockDeviceMappingTypeDef"]] = leader.get(
158
+ "BlockDeviceMappings"
159
+ )
126
160
  assert bdm is not None
127
- root_block_device: Optional[EbsInstanceBlockDeviceTypeDef] = None
161
+ root_block_device: Optional["EbsInstanceBlockDeviceTypeDef"] = None
128
162
  for device in bdm:
129
163
  if device["DeviceName"] == "/dev/xvda":
130
164
  root_block_device = device["Ebs"]
131
- assert root_block_device is not None # There should be a device named "/dev/xvda"
165
+ assert (
166
+ root_block_device is not None
167
+ ) # There should be a device named "/dev/xvda"
132
168
  assert root_block_device.get("VolumeId") is not None
133
169
  return root_block_device["VolumeId"]
134
170
 
@@ -141,18 +177,19 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
141
177
  """
142
178
  Helper method for _getScript to inject a script file at the configured script path, from text.
143
179
  """
144
- cluster = cluster_factory(provisioner='aws', zone=self.zone, clusterName=self.clusterName)
180
+ cluster = cluster_factory(
181
+ provisioner="aws", zone=self.zone, clusterName=self.clusterName
182
+ )
145
183
  leader = cluster.getLeader()
146
184
 
147
- self.sshUtil(['mkdir', '-p', self.scriptDir])
185
+ self.sshUtil(["mkdir", "-p", self.scriptDir])
148
186
 
149
- with tempfile.NamedTemporaryFile(mode='w') as t:
187
+ with tempfile.NamedTemporaryFile(mode="w") as t:
150
188
  # use appliance ssh method instead of sshutil so we can specify input param
151
189
  t.write(content)
152
190
  # This works to make writes visible on non-Windows
153
191
  t.flush()
154
- leader.injectFile(t.name, self.script(), 'toil_leader')
155
-
192
+ leader.injectFile(t.name, self.script(), "toil_leader")
156
193
 
157
194
  @abstractmethod
158
195
  def _runScript(self, toilOptions):
@@ -171,40 +208,50 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
171
208
  self.launchCluster()
172
209
  # get the leader so we know the IP address - we don't need to wait since create cluster
173
210
  # already insures the leader is running
174
- self.cluster = cluster_factory(provisioner='aws', zone=self.zone, clusterName=self.clusterName)
211
+ self.cluster = cluster_factory(
212
+ provisioner="aws", zone=self.zone, clusterName=self.clusterName
213
+ )
175
214
  self.leader = self.cluster.getLeader()
176
- self.sshUtil(['mkdir', '-p', self.scriptDir])
177
- self.sshUtil(['mkdir', '-p', self.dataDir])
215
+ self.sshUtil(["mkdir", "-p", self.scriptDir])
216
+ self.sshUtil(["mkdir", "-p", self.dataDir])
178
217
 
179
218
  assert len(self.cluster._getRoleNames()) == 1
180
219
  # --never-download prevents silent upgrades to pip, wheel and setuptools
181
- venv_command = ['virtualenv', '--system-site-packages', '--python', exactPython, '--never-download', self.venvDir]
220
+ venv_command = [
221
+ "virtualenv",
222
+ "--system-site-packages",
223
+ "--python",
224
+ exactPython,
225
+ "--never-download",
226
+ self.venvDir,
227
+ ]
182
228
  self.sshUtil(venv_command)
183
229
 
184
- log.info('Set up script...')
230
+ log.info("Set up script...")
185
231
  self._getScript()
186
232
 
187
- toilOptions = [self.jobStore,
188
- '--workDir=/var/lib/toil',
189
- '--clean=always',
190
- '--retryCount=2',
191
- '--logDebug',
192
- '--logFile=' + os.path.join(self.scriptDir, 'sort.log')
193
- ]
233
+ toilOptions = [
234
+ self.jobStore,
235
+ "--workDir=/var/lib/toil",
236
+ "--clean=always",
237
+ "--retryCount=2",
238
+ "--logDebug",
239
+ "--logFile=" + os.path.join(self.scriptDir, "sort.log"),
240
+ ]
194
241
 
195
242
  if preemptibleJobs:
196
- toilOptions.extend(['--defaultPreemptible'])
243
+ toilOptions.extend(["--defaultPreemptible"])
197
244
 
198
- log.info('Run script...')
245
+ log.info("Run script...")
199
246
  self._runScript(toilOptions)
200
247
 
201
248
  assert len(self.cluster._getRoleNames()) == 1
202
249
 
203
250
  volumeID = self.getRootVolID()
204
251
  self.cluster.destroyCluster()
205
- boto3_ec2: EC2Client = self.aws.client(region=self.region, service_name="ec2")
206
- volume_filter: FilterTypeDef = {"Name": "volume-id", "Values": [volumeID]}
207
- volumes: Optional[List[VolumeTypeDef]] = None
252
+ boto3_ec2: "EC2Client" = self.aws.client(region=self.region, service_name="ec2")
253
+ volume_filter: "FilterTypeDef" = {"Name": "volume-id", "Values": [volumeID]}
254
+ volumes: Optional[list["VolumeTypeDef"]] = None
208
255
  for attempt in range(6):
209
256
  # https://github.com/BD2KGenomics/toil/issues/1567
210
257
  # retry this for up to 1 minute until the volume disappears
@@ -214,7 +261,7 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
214
261
  break
215
262
  time.sleep(10)
216
263
  if volumes is None or len(volumes) > 0:
217
- self.fail('Volume with ID %s was not cleaned up properly' % volumeID)
264
+ self.fail("Volume with ID %s was not cleaned up properly" % volumeID)
218
265
 
219
266
  assert len(self.cluster._getRoleNames()) == 0
220
267
 
@@ -225,34 +272,49 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
225
272
  class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
226
273
  def __init__(self, name):
227
274
  super().__init__(name)
228
- self.clusterName = 'provisioner-test-' + str(uuid4())
275
+ self.clusterName = "provisioner-test-" + str(uuid4())
229
276
  self.requestedLeaderStorage = 80
230
- self.scriptName = 'sort.py'
277
+ self.scriptName = "sort.py"
231
278
 
232
279
  def setUp(self):
233
280
  super().setUp()
234
- self.jobStore = f'aws:{self.awsRegion()}:autoscale-{uuid4()}'
281
+ self.jobStore = f"aws:{self.awsRegion()}:autoscale-{uuid4()}"
235
282
 
236
283
  def _getScript(self):
237
284
  fileToSort = os.path.join(os.getcwd(), str(uuid4()))
238
- with open(fileToSort, 'w') as f:
285
+ with open(fileToSort, "w") as f:
239
286
  # Fixme: making this file larger causes the test to hang
240
- f.write('01234567890123456789012345678901')
241
- self.rsyncUtil(os.path.join(self._projectRootPath(), 'src/toil/test/sort/sort.py'), ':' + self.script())
242
- self.rsyncUtil(fileToSort, ':' + self.data('sortFile'))
287
+ f.write("01234567890123456789012345678901")
288
+ self.rsyncUtil(
289
+ os.path.join(self._projectRootPath(), "src/toil/test/sort/sort.py"),
290
+ ":" + self.script(),
291
+ )
292
+ self.rsyncUtil(fileToSort, ":" + self.data("sortFile"))
243
293
  os.unlink(fileToSort)
244
294
 
245
295
  def _runScript(self, toilOptions):
246
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
247
- '--nodeTypes=' + ",".join(self.instanceTypes),
248
- '--maxNodes=' + ",".join(self.numWorkers)])
249
- runCommand = [self.python(), self.script(), '--fileToSort=' + self.data('sortFile'), '--sseKey=' + self.data('sortFile')]
296
+ toilOptions.extend(
297
+ [
298
+ "--provisioner=aws",
299
+ "--batchSystem=mesos",
300
+ "--nodeTypes=" + ",".join(self.instanceTypes),
301
+ "--maxNodes=" + ",".join(self.numWorkers),
302
+ ]
303
+ )
304
+ runCommand = [
305
+ self.python(),
306
+ self.script(),
307
+ "--fileToSort=" + self.data("sortFile"),
308
+ "--sseKey=" + self.data("sortFile"),
309
+ ]
250
310
  runCommand.extend(toilOptions)
251
311
  self.sshUtil(runCommand)
252
312
 
253
313
  def launchCluster(self):
254
314
  # add arguments to test that we can specify leader storage
255
- self.createClusterUtil(args=['--leaderStorage', str(self.requestedLeaderStorage)])
315
+ self.createClusterUtil(
316
+ args=["--leaderStorage", str(self.requestedLeaderStorage)]
317
+ )
256
318
 
257
319
  def getRootVolID(self) -> str:
258
320
  """
@@ -261,10 +323,12 @@ class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
261
323
  :return: volumeID
262
324
  """
263
325
  volumeID = super().getRootVolID()
264
- boto3_ec2: EC2Client = self.aws.client(region=self.region, service_name="ec2")
265
- volume_filter: FilterTypeDef = {"Name": "volume-id", "Values": [volumeID]}
266
- volumes: DescribeVolumesResultTypeDef = boto3_ec2.describe_volumes(Filters=[volume_filter])
267
- root_volume: VolumeTypeDef = volumes["Volumes"][0] # should be first
326
+ boto3_ec2: "EC2Client" = self.aws.client(region=self.region, service_name="ec2")
327
+ volume_filter: "FilterTypeDef" = {"Name": "volume-id", "Values": [volumeID]}
328
+ volumes: "DescribeVolumesResultTypeDef" = boto3_ec2.describe_volumes(
329
+ Filters=[volume_filter]
330
+ )
331
+ root_volume: "VolumeTypeDef" = volumes["Volumes"][0] # should be first
268
332
  # test that the leader is given adequate storage
269
333
  self.assertGreaterEqual(root_volume["Size"], self.requestedLeaderStorage)
270
334
  return volumeID
@@ -273,21 +337,21 @@ class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
273
337
  @needs_aws_ec2
274
338
  def testAutoScale(self):
275
339
  self.instanceTypes = ["m5a.large"]
276
- self.numWorkers = ['2']
340
+ self.numWorkers = ["2"]
277
341
  self._test()
278
342
 
279
343
  @integrative
280
344
  @needs_aws_ec2
281
345
  def testSpotAutoScale(self):
282
346
  self.instanceTypes = ["m5a.large:%f" % self.spotBid]
283
- self.numWorkers = ['2']
347
+ self.numWorkers = ["2"]
284
348
  self._test(preemptibleJobs=True)
285
349
 
286
350
  @integrative
287
351
  @needs_aws_ec2
288
352
  def testSpotAutoScaleBalancingTypes(self):
289
353
  self.instanceTypes = ["m5.large/m5a.large:%f" % self.spotBid]
290
- self.numWorkers = ['2']
354
+ self.numWorkers = ["2"]
291
355
  self._test(preemptibleJobs=True)
292
356
 
293
357
 
@@ -296,23 +360,35 @@ class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
296
360
  @pytest.mark.timeout(2400)
297
361
  class AWSStaticAutoscaleTest(AWSAutoscaleTest):
298
362
  """Runs the tests on a statically provisioned cluster with autoscaling enabled."""
363
+
299
364
  def __init__(self, name):
300
365
  super().__init__(name)
301
366
  self.requestedNodeStorage = 20
302
367
 
303
368
  def launchCluster(self):
304
369
  from toil.lib.ec2 import wait_instances_running
305
- self.createClusterUtil(args=['--leaderStorage', str(self.requestedLeaderStorage),
306
- '--nodeTypes', ",".join(self.instanceTypes),
307
- '-w', ",".join(self.numWorkers),
308
- '--nodeStorage', str(self.requestedLeaderStorage)])
309
370
 
310
- self.cluster = cluster_factory(provisioner='aws', zone=self.zone, clusterName=self.clusterName)
371
+ self.createClusterUtil(
372
+ args=[
373
+ "--leaderStorage",
374
+ str(self.requestedLeaderStorage),
375
+ "--nodeTypes",
376
+ ",".join(self.instanceTypes),
377
+ "-w",
378
+ ",".join(self.numWorkers),
379
+ "--nodeStorage",
380
+ str(self.requestedLeaderStorage),
381
+ ]
382
+ )
383
+
384
+ self.cluster = cluster_factory(
385
+ provisioner="aws", zone=self.zone, clusterName=self.clusterName
386
+ )
311
387
  # We need to wait a little bit here because the workers might not be
312
388
  # visible to EC2 read requests immediately after the create returns,
313
389
  # which is the last thing that starting the cluster does.
314
390
  time.sleep(10)
315
- nodes: List[InstanceTypeDef] = self.cluster._get_nodes_in_cluster_boto3()
391
+ nodes: list["InstanceTypeDef"] = self.cluster._get_nodes_in_cluster_boto3()
316
392
  nodes.sort(key=lambda x: x.get("LaunchTime"))
317
393
  # assuming that leader is first
318
394
  workers = nodes[1:]
@@ -321,29 +397,49 @@ class AWSStaticAutoscaleTest(AWSAutoscaleTest):
321
397
  # test that workers have expected storage size
322
398
  # just use the first worker
323
399
  worker = workers[0]
324
- boto3_ec2: EC2Client = self.aws.client(region=self.region, service_name="ec2")
400
+ boto3_ec2: "EC2Client" = self.aws.client(region=self.region, service_name="ec2")
325
401
 
326
- worker: InstanceTypeDef = next(wait_instances_running(boto3_ec2, [worker]))
402
+ worker: "InstanceTypeDef" = next(wait_instances_running(boto3_ec2, [worker]))
327
403
 
328
- bdm: Optional[List[InstanceBlockDeviceMappingTypeDef]] = worker.get("BlockDeviceMappings")
404
+ bdm: Optional[list["InstanceBlockDeviceMappingTypeDef"]] = worker.get(
405
+ "BlockDeviceMappings"
406
+ )
329
407
  assert bdm is not None
330
- root_block_device: Optional[EbsInstanceBlockDeviceTypeDef] = None
408
+ root_block_device: Optional["EbsInstanceBlockDeviceTypeDef"] = None
331
409
  for device in bdm:
332
410
  if device["DeviceName"] == "/dev/xvda":
333
411
  root_block_device = device["Ebs"]
334
412
  assert root_block_device is not None
335
- assert root_block_device.get("VolumeId") is not None # TypedDicts cannot have runtime type checks
336
-
337
- volume_filter: FilterTypeDef = {"Name": "volume-id", "Values": [root_block_device["VolumeId"]]}
338
- root_volume: VolumeTypeDef = boto3_ec2.describe_volumes(Filters=[volume_filter])["Volumes"][0] # should be first
413
+ assert (
414
+ root_block_device.get("VolumeId") is not None
415
+ ) # TypedDicts cannot have runtime type checks
416
+
417
+ volume_filter: "FilterTypeDef" = {
418
+ "Name": "volume-id",
419
+ "Values": [root_block_device["VolumeId"]],
420
+ }
421
+ root_volume: "VolumeTypeDef" = boto3_ec2.describe_volumes(
422
+ Filters=[volume_filter]
423
+ )["Volumes"][
424
+ 0
425
+ ] # should be first
339
426
  self.assertGreaterEqual(root_volume.get("Size"), self.requestedNodeStorage)
340
427
 
341
428
  def _runScript(self, toilOptions):
342
429
  # Autoscale even though we have static nodes
343
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
344
- '--nodeTypes=' + ",".join(self.instanceTypes),
345
- '--maxNodes=' + ",".join(self.numWorkers)])
346
- runCommand = [self.python(), self.script(), '--fileToSort=' + self.data('sortFile')]
430
+ toilOptions.extend(
431
+ [
432
+ "--provisioner=aws",
433
+ "--batchSystem=mesos",
434
+ "--nodeTypes=" + ",".join(self.instanceTypes),
435
+ "--maxNodes=" + ",".join(self.numWorkers),
436
+ ]
437
+ )
438
+ runCommand = [
439
+ self.python(),
440
+ self.script(),
441
+ "--fileToSort=" + self.data("sortFile"),
442
+ ]
347
443
  runCommand.extend(toilOptions)
348
444
  self.sshUtil(runCommand)
349
445
 
@@ -352,23 +448,39 @@ class AWSStaticAutoscaleTest(AWSAutoscaleTest):
352
448
  @pytest.mark.timeout(1200)
353
449
  class AWSManagedAutoscaleTest(AWSAutoscaleTest):
354
450
  """Runs the tests on a self-scaling Kubernetes cluster."""
451
+
355
452
  def __init__(self, name):
356
453
  super().__init__(name)
357
454
  self.requestedNodeStorage = 20
358
455
 
359
456
  def launchCluster(self):
360
- self.createClusterUtil(args=['--leaderStorage', str(self.requestedLeaderStorage),
361
- '--nodeTypes', ",".join(self.instanceTypes),
362
- '--workers', ",".join([f'0-{c}' for c in self.numWorkers]),
363
- '--nodeStorage', str(self.requestedLeaderStorage),
364
- '--clusterType', 'kubernetes'])
365
-
366
- self.cluster = cluster_factory(provisioner='aws', zone=self.zone, clusterName=self.clusterName)
457
+ self.createClusterUtil(
458
+ args=[
459
+ "--leaderStorage",
460
+ str(self.requestedLeaderStorage),
461
+ "--nodeTypes",
462
+ ",".join(self.instanceTypes),
463
+ "--workers",
464
+ ",".join([f"0-{c}" for c in self.numWorkers]),
465
+ "--nodeStorage",
466
+ str(self.requestedLeaderStorage),
467
+ "--clusterType",
468
+ "kubernetes",
469
+ ]
470
+ )
471
+
472
+ self.cluster = cluster_factory(
473
+ provisioner="aws", zone=self.zone, clusterName=self.clusterName
474
+ )
367
475
 
368
476
  def _runScript(self, toilOptions):
369
477
  # Don't use the provisioner, and use Kubernetes instead of Mesos
370
- toilOptions.extend(['--batchSystem=kubernetes'])
371
- runCommand = [self.python(), self.script(), '--fileToSort=' + self.data('sortFile')]
478
+ toilOptions.extend(["--batchSystem=kubernetes"])
479
+ runCommand = [
480
+ self.python(),
481
+ self.script(),
482
+ "--fileToSort=" + self.data("sortFile"),
483
+ ]
372
484
  runCommand.extend(toilOptions)
373
485
  self.sshUtil(runCommand)
374
486
 
@@ -379,37 +491,51 @@ class AWSManagedAutoscaleTest(AWSAutoscaleTest):
379
491
  class AWSAutoscaleTestMultipleNodeTypes(AbstractAWSAutoscaleTest):
380
492
  def __init__(self, name):
381
493
  super().__init__(name)
382
- self.clusterName = 'provisioner-test-' + str(uuid4())
494
+ self.clusterName = "provisioner-test-" + str(uuid4())
383
495
 
384
496
  def setUp(self):
385
497
  super().setUp()
386
- self.jobStore = f'aws:{self.awsRegion()}:autoscale-{uuid4()}'
498
+ self.jobStore = f"aws:{self.awsRegion()}:autoscale-{uuid4()}"
387
499
 
388
500
  def _getScript(self):
389
- sseKeyFile = os.path.join(os.getcwd(), 'keyFile')
390
- with open(sseKeyFile, 'w') as f:
391
- f.write('01234567890123456789012345678901')
392
- self.rsyncUtil(os.path.join(self._projectRootPath(), 'src/toil/test/sort/sort.py'), ':' + self.script())
393
- self.rsyncUtil(sseKeyFile, ':' + self.data('keyFile'))
501
+ sseKeyFile = os.path.join(os.getcwd(), "keyFile")
502
+ with open(sseKeyFile, "w") as f:
503
+ f.write("01234567890123456789012345678901")
504
+ self.rsyncUtil(
505
+ os.path.join(self._projectRootPath(), "src/toil/test/sort/sort.py"),
506
+ ":" + self.script(),
507
+ )
508
+ self.rsyncUtil(sseKeyFile, ":" + self.data("keyFile"))
394
509
  os.unlink(sseKeyFile)
395
510
 
396
511
  def _runScript(self, toilOptions):
397
512
  # Set memory requirements so that sort jobs can be run
398
513
  # on small instances, but merge jobs must be run on large
399
514
  # instances
400
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
401
- '--nodeTypes=' + ",".join(self.instanceTypes),
402
- '--maxNodes=' + ",".join(self.numWorkers)])
403
- runCommand = [self.python(), self.script(), '--fileToSort=/home/s3am/bin/asadmin', '--sortMemory=0.6G', '--mergeMemory=3.0G']
515
+ toilOptions.extend(
516
+ [
517
+ "--provisioner=aws",
518
+ "--batchSystem=mesos",
519
+ "--nodeTypes=" + ",".join(self.instanceTypes),
520
+ "--maxNodes=" + ",".join(self.numWorkers),
521
+ ]
522
+ )
523
+ runCommand = [
524
+ self.python(),
525
+ self.script(),
526
+ "--fileToSort=/home/s3am/bin/asadmin",
527
+ "--sortMemory=0.6G",
528
+ "--mergeMemory=3.0G",
529
+ ]
404
530
  runCommand.extend(toilOptions)
405
- runCommand.append('--sseKey=' + self.data('keyFile'))
531
+ runCommand.append("--sseKey=" + self.data("keyFile"))
406
532
  self.sshUtil(runCommand)
407
533
 
408
534
  @integrative
409
535
  @needs_aws_ec2
410
536
  def testAutoScale(self):
411
537
  self.instanceTypes = ["t2.small", "m5a.large"]
412
- self.numWorkers = ['2', '1']
538
+ self.numWorkers = ["2", "1"]
413
539
  self._test()
414
540
 
415
541
 
@@ -418,16 +544,17 @@ class AWSAutoscaleTestMultipleNodeTypes(AbstractAWSAutoscaleTest):
418
544
  @pytest.mark.timeout(1200)
419
545
  class AWSRestartTest(AbstractAWSAutoscaleTest):
420
546
  """This test insures autoscaling works on a restarted Toil run."""
547
+
421
548
  def __init__(self, name):
422
549
  super().__init__(name)
423
- self.clusterName = 'restart-test-' + str(uuid4())
424
- self.scriptName = 'restartScript.py'
550
+ self.clusterName = "restart-test-" + str(uuid4())
551
+ self.scriptName = "restartScript.py"
425
552
 
426
553
  def setUp(self):
427
554
  super().setUp()
428
- self.instanceTypes = ['t2.small']
429
- self.numWorkers = ['1']
430
- self.jobStore = f'aws:{self.awsRegion()}:restart-{uuid4()}'
555
+ self.instanceTypes = ["t2.small"]
556
+ self.numWorkers = ["1"]
557
+ self.jobStore = f"aws:{self.awsRegion()}:restart-{uuid4()}"
431
558
 
432
559
  def _getScript(self):
433
560
  def restartScript():
@@ -438,38 +565,56 @@ class AWSRestartTest(AbstractAWSAutoscaleTest):
438
565
  from toil.job import Job
439
566
 
440
567
  def f0(job):
441
- if 'FAIL' in os.environ:
442
- raise RuntimeError('failed on purpose')
568
+ if "FAIL" in os.environ:
569
+ raise RuntimeError("failed on purpose")
443
570
 
444
- if __name__ == '__main__':
571
+ if __name__ == "__main__":
445
572
  parser = ArgumentParser()
446
573
  Job.Runner.addToilOptions(parser)
447
574
  options = parser.parse_args()
448
- rootJob = Job.wrapJobFn(f0, cores=0.5, memory='50 M', disk='50 M')
575
+ rootJob = Job.wrapJobFn(f0, cores=0.5, memory="50 M", disk="50 M")
449
576
  Job.Runner.startToil(rootJob, options)
450
577
 
451
- script = dedent('\n'.join(getsource(restartScript).split('\n')[1:]))
578
+ script = dedent("\n".join(getsource(restartScript).split("\n")[1:]))
452
579
  self.putScript(script)
453
580
 
454
581
  def _runScript(self, toilOptions):
455
582
  # Use the provisioner in the workflow
456
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
457
- '--nodeTypes=' + ",".join(self.instanceTypes),
458
- '--maxNodes=' + ",".join(self.numWorkers)])
583
+ toilOptions.extend(
584
+ [
585
+ "--provisioner=aws",
586
+ "--batchSystem=mesos",
587
+ "--nodeTypes=" + ",".join(self.instanceTypes),
588
+ "--maxNodes=" + ",".join(self.numWorkers),
589
+ ]
590
+ )
459
591
  # clean = onSuccess
460
- disallowedOptions = ['--clean=always', '--retryCount=2']
461
- newOptions = [option for option in toilOptions if option not in disallowedOptions]
592
+ disallowedOptions = ["--clean=always", "--retryCount=2"]
593
+ newOptions = [
594
+ option for option in toilOptions if option not in disallowedOptions
595
+ ]
462
596
  try:
463
597
  # include a default memory - on restart the minimum memory requirement is the default, usually 2 GB
464
- command = [self.python(), self.script(), '--setEnv', 'FAIL=true', '--defaultMemory=50000000']
598
+ command = [
599
+ self.python(),
600
+ self.script(),
601
+ "--setEnv",
602
+ "FAIL=true",
603
+ "--defaultMemory=50000000",
604
+ ]
465
605
  command.extend(newOptions)
466
606
  self.sshUtil(command)
467
607
  except subprocess.CalledProcessError:
468
608
  pass
469
609
  else:
470
- self.fail('Command succeeded when we expected failure')
610
+ self.fail("Command succeeded when we expected failure")
471
611
  with timeLimit(600):
472
- command = [self.python(), self.script(), '--restart', '--defaultMemory=50000000']
612
+ command = [
613
+ self.python(),
614
+ self.script(),
615
+ "--restart",
616
+ "--defaultMemory=50000000",
617
+ ]
473
618
  command.extend(toilOptions)
474
619
  self.sshUtil(command)
475
620
 
@@ -483,14 +628,17 @@ class AWSRestartTest(AbstractAWSAutoscaleTest):
483
628
  class PreemptibleDeficitCompensationTest(AbstractAWSAutoscaleTest):
484
629
  def __init__(self, name):
485
630
  super().__init__(name)
486
- self.clusterName = 'deficit-test-' + str(uuid4())
487
- self.scriptName = 'userScript.py'
631
+ self.clusterName = "deficit-test-" + str(uuid4())
632
+ self.scriptName = "userScript.py"
488
633
 
489
634
  def setUp(self):
490
635
  super().setUp()
491
- self.instanceTypes = ['m5a.large:0.01', "m5a.large"] # instance needs to be available on the spot market
492
- self.numWorkers = ['1', '1']
493
- self.jobStore = f'aws:{self.awsRegion()}:deficit-{uuid4()}'
636
+ self.instanceTypes = [
637
+ "m5a.large:0.01",
638
+ "m5a.large",
639
+ ] # instance needs to be available on the spot market
640
+ self.numWorkers = ["1", "1"]
641
+ self.jobStore = f"aws:{self.awsRegion()}:deficit-{uuid4()}"
494
642
 
495
643
  def test(self):
496
644
  self._test(preemptibleJobs=True)
@@ -507,10 +655,10 @@ class PreemptibleDeficitCompensationTest(AbstractAWSAutoscaleTest):
507
655
  # we will observe a deficit of preemptible nodes that the non-preemptible scaler will
508
656
  # compensate for by spinning up non-preemptible nodes instead.
509
657
  #
510
- def job(job, disk='10M', cores=1, memory='10M', preemptible=True):
658
+ def job(job, disk="10M", cores=1, memory="10M", preemptible=True):
511
659
  pass
512
660
 
513
- if __name__ == '__main__':
661
+ if __name__ == "__main__":
514
662
  options = Job.Runner.getDefaultArgumentParser().parse_args()
515
663
  with Toil(options) as toil:
516
664
  if toil.config.restart:
@@ -518,14 +666,19 @@ class PreemptibleDeficitCompensationTest(AbstractAWSAutoscaleTest):
518
666
  else:
519
667
  toil.start(Job.wrapJobFn(job))
520
668
 
521
- script = dedent('\n'.join(getsource(userScript).split('\n')[1:]))
669
+ script = dedent("\n".join(getsource(userScript).split("\n")[1:]))
522
670
  self.putScript(script)
523
671
 
524
672
  def _runScript(self, toilOptions):
525
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
526
- '--nodeTypes=' + ",".join(self.instanceTypes),
527
- '--maxNodes=' + ",".join(self.numWorkers)])
528
- toilOptions.extend(['--preemptibleCompensation=1.0'])
673
+ toilOptions.extend(
674
+ [
675
+ "--provisioner=aws",
676
+ "--batchSystem=mesos",
677
+ "--nodeTypes=" + ",".join(self.instanceTypes),
678
+ "--maxNodes=" + ",".join(self.numWorkers),
679
+ ]
680
+ )
681
+ toilOptions.extend(["--preemptibleCompensation=1.0"])
529
682
  command = [self.python(), self.script()]
530
683
  command.extend(toilOptions)
531
684
  self.sshUtil(command)