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.
Files changed (193) hide show
  1. toil/__init__.py +122 -315
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +173 -89
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
  5. toil/batchSystems/awsBatch.py +244 -135
  6. toil/batchSystems/cleanup_support.py +26 -16
  7. toil/batchSystems/contained_executor.py +31 -28
  8. toil/batchSystems/gridengine.py +86 -50
  9. toil/batchSystems/htcondor.py +166 -89
  10. toil/batchSystems/kubernetes.py +632 -382
  11. toil/batchSystems/local_support.py +20 -15
  12. toil/batchSystems/lsf.py +134 -81
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +290 -151
  16. toil/batchSystems/mesos/executor.py +79 -50
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +46 -28
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +296 -125
  21. toil/batchSystems/slurm.py +603 -138
  22. toil/batchSystems/torque.py +47 -33
  23. toil/bus.py +186 -76
  24. toil/common.py +664 -368
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1136 -483
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +63 -42
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +140 -60
  32. toil/fileStores/cachingFileStore.py +717 -269
  33. toil/fileStores/nonCachingFileStore.py +116 -87
  34. toil/job.py +1225 -368
  35. toil/jobStores/abstractJobStore.py +416 -266
  36. toil/jobStores/aws/jobStore.py +863 -477
  37. toil/jobStores/aws/utils.py +201 -120
  38. toil/jobStores/conftest.py +3 -2
  39. toil/jobStores/fileJobStore.py +292 -154
  40. toil/jobStores/googleJobStore.py +140 -74
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +668 -272
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +74 -31
  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 +214 -39
  49. toil/lib/aws/utils.py +287 -231
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +104 -47
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +361 -199
  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 +5 -3
  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 +141 -15
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +66 -21
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +68 -15
  71. toil/lib/retry.py +126 -81
  72. toil/lib/threading.py +299 -82
  73. toil/lib/throttle.py +16 -15
  74. toil/options/common.py +843 -409
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +73 -17
  78. toil/provisioners/__init__.py +117 -46
  79. toil/provisioners/abstractProvisioner.py +332 -157
  80. toil/provisioners/aws/__init__.py +70 -33
  81. toil/provisioners/aws/awsProvisioner.py +1145 -715
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +155 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +128 -62
  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 +224 -70
  98. toil/test/__init__.py +282 -183
  99. toil/test/batchSystems/batchSystemTest.py +460 -210
  100. toil/test/batchSystems/batch_system_plugin_test.py +90 -0
  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 +110 -49
  104. toil/test/cactus/__init__.py +0 -0
  105. toil/test/cactus/test_cactus_integration.py +56 -0
  106. toil/test/cwl/cwlTest.py +496 -287
  107. toil/test/cwl/measure_default_memory.cwl +12 -0
  108. toil/test/cwl/not_run_required_input.cwl +29 -0
  109. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  110. toil/test/cwl/seqtk_seq.cwl +1 -1
  111. toil/test/docs/scriptsTest.py +69 -46
  112. toil/test/jobStores/jobStoreTest.py +427 -264
  113. toil/test/lib/aws/test_iam.py +118 -50
  114. toil/test/lib/aws/test_s3.py +16 -9
  115. toil/test/lib/aws/test_utils.py +5 -6
  116. toil/test/lib/dockerTest.py +118 -141
  117. toil/test/lib/test_conversions.py +113 -115
  118. toil/test/lib/test_ec2.py +58 -50
  119. toil/test/lib/test_integration.py +104 -0
  120. toil/test/lib/test_misc.py +12 -5
  121. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  122. toil/test/mesos/helloWorld.py +7 -6
  123. toil/test/mesos/stress.py +25 -20
  124. toil/test/options/__init__.py +13 -0
  125. toil/test/options/options.py +42 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
  127. toil/test/provisioners/clusterScalerTest.py +440 -250
  128. toil/test/provisioners/clusterTest.py +166 -44
  129. toil/test/provisioners/gceProvisionerTest.py +174 -100
  130. toil/test/provisioners/provisionerTest.py +25 -13
  131. toil/test/provisioners/restartScript.py +5 -4
  132. toil/test/server/serverTest.py +188 -141
  133. toil/test/sort/restart_sort.py +137 -68
  134. toil/test/sort/sort.py +134 -66
  135. toil/test/sort/sortTest.py +91 -49
  136. toil/test/src/autoDeploymentTest.py +141 -101
  137. toil/test/src/busTest.py +20 -18
  138. toil/test/src/checkpointTest.py +8 -2
  139. toil/test/src/deferredFunctionTest.py +49 -35
  140. toil/test/src/dockerCheckTest.py +32 -24
  141. toil/test/src/environmentTest.py +135 -0
  142. toil/test/src/fileStoreTest.py +539 -272
  143. toil/test/src/helloWorldTest.py +7 -4
  144. toil/test/src/importExportFileTest.py +61 -31
  145. toil/test/src/jobDescriptionTest.py +46 -21
  146. toil/test/src/jobEncapsulationTest.py +2 -0
  147. toil/test/src/jobFileStoreTest.py +74 -50
  148. toil/test/src/jobServiceTest.py +187 -73
  149. toil/test/src/jobTest.py +121 -71
  150. toil/test/src/miscTests.py +19 -18
  151. toil/test/src/promisedRequirementTest.py +82 -36
  152. toil/test/src/promisesTest.py +7 -6
  153. toil/test/src/realtimeLoggerTest.py +10 -6
  154. toil/test/src/regularLogTest.py +71 -37
  155. toil/test/src/resourceTest.py +80 -49
  156. toil/test/src/restartDAGTest.py +36 -22
  157. toil/test/src/resumabilityTest.py +9 -2
  158. toil/test/src/retainTempDirTest.py +45 -14
  159. toil/test/src/systemTest.py +12 -8
  160. toil/test/src/threadingTest.py +44 -25
  161. toil/test/src/toilContextManagerTest.py +10 -7
  162. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  163. toil/test/src/workerTest.py +73 -23
  164. toil/test/utils/toilDebugTest.py +103 -33
  165. toil/test/utils/toilKillTest.py +4 -5
  166. toil/test/utils/utilsTest.py +245 -106
  167. toil/test/wdl/wdltoil_test.py +818 -149
  168. toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
  169. toil/toilState.py +120 -35
  170. toil/utils/toilConfig.py +13 -4
  171. toil/utils/toilDebugFile.py +44 -27
  172. toil/utils/toilDebugJob.py +214 -27
  173. toil/utils/toilDestroyCluster.py +11 -6
  174. toil/utils/toilKill.py +8 -3
  175. toil/utils/toilLaunchCluster.py +256 -140
  176. toil/utils/toilMain.py +37 -16
  177. toil/utils/toilRsyncCluster.py +32 -14
  178. toil/utils/toilSshCluster.py +49 -22
  179. toil/utils/toilStats.py +356 -273
  180. toil/utils/toilStatus.py +292 -139
  181. toil/utils/toilUpdateEC2Instances.py +3 -1
  182. toil/version.py +12 -12
  183. toil/wdl/utils.py +5 -5
  184. toil/wdl/wdltoil.py +3913 -1033
  185. toil/worker.py +367 -184
  186. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
  187. toil-8.0.0.dist-info/METADATA +173 -0
  188. toil-8.0.0.dist-info/RECORD +253 -0
  189. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  190. toil-6.1.0a1.dist-info/METADATA +0 -125
  191. toil-6.1.0a1.dist-info/RECORD +0 -237
  192. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  193. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
@@ -19,22 +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 TYPE_CHECKING, Optional
22
23
  from uuid import uuid4
23
24
 
24
25
  import pytest
25
26
 
26
27
  from toil.provisioners import cluster_factory
27
28
  from toil.provisioners.aws.awsProvisioner import AWSProvisioner
28
- from toil.test import (ToilTest,
29
- integrative,
30
- needs_aws_ec2,
31
- needs_fetchable_appliance,
32
- needs_mesos,
33
- slow,
34
- 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
+ )
35
38
  from toil.test.provisioners.clusterTest import AbstractClusterTest
36
39
  from toil.version import exactPython
37
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
+
38
53
  log = logging.getLogger(__name__)
39
54
 
40
55
 
@@ -46,11 +61,13 @@ class AWSProvisionerBenchTest(ToilTest):
46
61
  # Needs to talk to EC2 for image discovery
47
62
  @needs_aws_ec2
48
63
  def test_AMI_finding(self):
49
- for zone in ['us-west-2a', 'eu-central-1a', 'sa-east-1b']:
50
- 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
+ )
51
68
  ami = provisioner._discoverAMI()
52
69
  # Make sure we got an AMI and it looks plausible
53
- assert(ami.startswith('ami-'))
70
+ assert ami.startswith("ami-")
54
71
 
55
72
  @needs_aws_ec2
56
73
  def test_read_write_global_files(self):
@@ -58,8 +75,16 @@ class AWSProvisionerBenchTest(ToilTest):
58
75
  Make sure the `_write_file_to_cloud()` and `_read_file_from_cloud()`
59
76
  functions of the AWS provisioner work as intended.
60
77
  """
61
- provisioner = AWSProvisioner(f'aws-provisioner-test-{uuid4()}', 'mesos', 'us-west-2a', 50, None, None)
62
- 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"
63
88
  contents = b"Hello, this is a test."
64
89
 
65
90
  try:
@@ -80,23 +105,23 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
80
105
  def __init__(self, methodName):
81
106
  super().__init__(methodName=methodName)
82
107
  self.instanceTypes = ["m5a.large"]
83
- self.clusterName = 'aws-provisioner-test-' + str(uuid4())
84
- self.numWorkers = ['2']
108
+ self.clusterName = "aws-provisioner-test-" + str(uuid4())
109
+ self.numWorkers = ["2"]
85
110
  self.numSamples = 2
86
111
  self.spotBid = 0.15
87
112
  # We can't dump our user script right in /tmp or /home, because hot
88
113
  # deploy refuses to zip up those whole directories. So we make sure to
89
114
  # have a subdirectory to upload the script to.
90
- self.scriptDir = '/tmp/t'
115
+ self.scriptDir = "/tmp/t"
91
116
  # Where should we put our virtualenv?
92
- self.venvDir = '/tmp/venv'
117
+ self.venvDir = "/tmp/venv"
93
118
  # Where should we put our data to work on?
94
119
  # Must exist in the Toil container; the leader will try to rsync to it
95
120
  # (for the SSE key) and not create it.
96
- self.dataDir = '/tmp'
121
+ self.dataDir = "/tmp"
97
122
  # What filename should we use for our script (without path)?
98
123
  # Can be changed by derived tests.
99
- self.scriptName = 'test_script.py'
124
+ self.scriptName = "test_script.py"
100
125
 
101
126
  def script(self):
102
127
  """
@@ -111,17 +136,37 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
111
136
  return os.path.join(self.dataDir, filename)
112
137
 
113
138
  def rsyncUtil(self, src, dest):
114
- subprocess.check_call(['toil', 'rsync-cluster', '--insecure', '-p=aws', '-z', self.zone, self.clusterName] + [src, dest])
115
-
116
- def getRootVolID(self):
117
- instances = self.cluster._get_nodes_in_cluster()
118
- instances.sort(key=lambda x: x.launch_time)
119
- leader = instances[0] # assume leader was launched first
120
-
121
- from boto.ec2.blockdevicemapping import BlockDeviceType
122
- rootBlockDevice = leader.block_device_mapping["/dev/xvda"]
123
- assert isinstance(rootBlockDevice, BlockDeviceType)
124
- return rootBlockDevice.volume_id
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
+ )
151
+
152
+ def getRootVolID(self) -> str:
153
+ instances: list["InstanceTypeDef"] = self.cluster._get_nodes_in_cluster_boto3()
154
+ instances.sort(key=lambda x: x.get("LaunchTime"))
155
+ leader: "InstanceTypeDef" = instances[0] # assume leader was launched first
156
+
157
+ bdm: Optional[list["InstanceBlockDeviceMappingTypeDef"]] = leader.get(
158
+ "BlockDeviceMappings"
159
+ )
160
+ assert bdm is not None
161
+ root_block_device: Optional["EbsInstanceBlockDeviceTypeDef"] = None
162
+ for device in bdm:
163
+ if device["DeviceName"] == "/dev/xvda":
164
+ root_block_device = device["Ebs"]
165
+ assert (
166
+ root_block_device is not None
167
+ ) # There should be a device named "/dev/xvda"
168
+ assert root_block_device.get("VolumeId") is not None
169
+ return root_block_device["VolumeId"]
125
170
 
126
171
  @abstractmethod
127
172
  def _getScript(self):
@@ -132,18 +177,19 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
132
177
  """
133
178
  Helper method for _getScript to inject a script file at the configured script path, from text.
134
179
  """
135
- 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
+ )
136
183
  leader = cluster.getLeader()
137
184
 
138
- self.sshUtil(['mkdir', '-p', self.scriptDir])
185
+ self.sshUtil(["mkdir", "-p", self.scriptDir])
139
186
 
140
- with tempfile.NamedTemporaryFile(mode='w') as t:
187
+ with tempfile.NamedTemporaryFile(mode="w") as t:
141
188
  # use appliance ssh method instead of sshutil so we can specify input param
142
189
  t.write(content)
143
190
  # This works to make writes visible on non-Windows
144
191
  t.flush()
145
- leader.injectFile(t.name, self.script(), 'toil_leader')
146
-
192
+ leader.injectFile(t.name, self.script(), "toil_leader")
147
193
 
148
194
  @abstractmethod
149
195
  def _runScript(self, toilOptions):
@@ -162,51 +208,60 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
162
208
  self.launchCluster()
163
209
  # get the leader so we know the IP address - we don't need to wait since create cluster
164
210
  # already insures the leader is running
165
- 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
+ )
166
214
  self.leader = self.cluster.getLeader()
167
- self.sshUtil(['mkdir', '-p', self.scriptDir])
168
- self.sshUtil(['mkdir', '-p', self.dataDir])
215
+ self.sshUtil(["mkdir", "-p", self.scriptDir])
216
+ self.sshUtil(["mkdir", "-p", self.dataDir])
169
217
 
170
218
  assert len(self.cluster._getRoleNames()) == 1
171
219
  # --never-download prevents silent upgrades to pip, wheel and setuptools
172
- 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
+ ]
173
228
  self.sshUtil(venv_command)
174
229
 
175
- log.info('Set up script...')
230
+ log.info("Set up script...")
176
231
  self._getScript()
177
232
 
178
- toilOptions = [self.jobStore,
179
- '--workDir=/var/lib/toil',
180
- '--clean=always',
181
- '--retryCount=2',
182
- '--logDebug',
183
- '--logFile=' + os.path.join(self.scriptDir, 'sort.log')
184
- ]
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
+ ]
185
241
 
186
242
  if preemptibleJobs:
187
- toilOptions.extend(['--defaultPreemptible'])
243
+ toilOptions.extend(["--defaultPreemptible"])
188
244
 
189
- log.info('Run script...')
245
+ log.info("Run script...")
190
246
  self._runScript(toilOptions)
191
247
 
192
248
  assert len(self.cluster._getRoleNames()) == 1
193
249
 
194
- from boto.exception import EC2ResponseError
195
250
  volumeID = self.getRootVolID()
196
251
  self.cluster.destroyCluster()
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
197
255
  for attempt in range(6):
198
256
  # https://github.com/BD2KGenomics/toil/issues/1567
199
257
  # retry this for up to 1 minute until the volume disappears
200
- try:
201
- self.boto2_ec2.get_all_volumes(volume_ids=[volumeID])
202
- time.sleep(10)
203
- except EC2ResponseError as e:
204
- if e.status == 400 and 'InvalidVolume.NotFound' in e.code:
205
- break
206
- else:
207
- raise
208
- else:
209
- self.fail('Volume with ID %s was not cleaned up properly' % volumeID)
258
+ volumes = boto3_ec2.describe_volumes(Filters=[volume_filter])["Volumes"]
259
+ if len(volumes) == 0:
260
+ # None are left, so they have been properly deleted
261
+ break
262
+ time.sleep(10)
263
+ if volumes is None or len(volumes) > 0:
264
+ self.fail("Volume with ID %s was not cleaned up properly" % volumeID)
210
265
 
211
266
  assert len(self.cluster._getRoleNames()) == 0
212
267
 
@@ -217,66 +272,86 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
217
272
  class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
218
273
  def __init__(self, name):
219
274
  super().__init__(name)
220
- self.clusterName = 'provisioner-test-' + str(uuid4())
275
+ self.clusterName = "provisioner-test-" + str(uuid4())
221
276
  self.requestedLeaderStorage = 80
222
- self.scriptName = 'sort.py'
277
+ self.scriptName = "sort.py"
223
278
 
224
279
  def setUp(self):
225
280
  super().setUp()
226
- self.jobStore = f'aws:{self.awsRegion()}:autoscale-{uuid4()}'
281
+ self.jobStore = f"aws:{self.awsRegion()}:autoscale-{uuid4()}"
227
282
 
228
283
  def _getScript(self):
229
284
  fileToSort = os.path.join(os.getcwd(), str(uuid4()))
230
- with open(fileToSort, 'w') as f:
285
+ with open(fileToSort, "w") as f:
231
286
  # Fixme: making this file larger causes the test to hang
232
- f.write('01234567890123456789012345678901')
233
- self.rsyncUtil(os.path.join(self._projectRootPath(), 'src/toil/test/sort/sort.py'), ':' + self.script())
234
- 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"))
235
293
  os.unlink(fileToSort)
236
294
 
237
295
  def _runScript(self, toilOptions):
238
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
239
- '--nodeTypes=' + ",".join(self.instanceTypes),
240
- '--maxNodes=' + ",".join(self.numWorkers)])
241
- 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
+ ]
242
310
  runCommand.extend(toilOptions)
243
311
  self.sshUtil(runCommand)
244
312
 
245
313
  def launchCluster(self):
246
314
  # add arguments to test that we can specify leader storage
247
- self.createClusterUtil(args=['--leaderStorage', str(self.requestedLeaderStorage)])
315
+ self.createClusterUtil(
316
+ args=["--leaderStorage", str(self.requestedLeaderStorage)]
317
+ )
248
318
 
249
- def getRootVolID(self):
319
+ def getRootVolID(self) -> str:
250
320
  """
251
321
  Adds in test to check that EBS volume is build with adequate size.
252
322
  Otherwise is functionally equivalent to parent.
253
323
  :return: volumeID
254
324
  """
255
325
  volumeID = super().getRootVolID()
256
- rootVolume = self.boto2_ec2.get_all_volumes(volume_ids=[volumeID])[0]
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
257
332
  # test that the leader is given adequate storage
258
- self.assertGreaterEqual(rootVolume.size, self.requestedLeaderStorage)
333
+ self.assertGreaterEqual(root_volume["Size"], self.requestedLeaderStorage)
259
334
  return volumeID
260
335
 
261
336
  @integrative
262
337
  @needs_aws_ec2
263
338
  def testAutoScale(self):
264
339
  self.instanceTypes = ["m5a.large"]
265
- self.numWorkers = ['2']
340
+ self.numWorkers = ["2"]
266
341
  self._test()
267
342
 
268
343
  @integrative
269
344
  @needs_aws_ec2
270
345
  def testSpotAutoScale(self):
271
346
  self.instanceTypes = ["m5a.large:%f" % self.spotBid]
272
- self.numWorkers = ['2']
347
+ self.numWorkers = ["2"]
273
348
  self._test(preemptibleJobs=True)
274
349
 
275
350
  @integrative
276
351
  @needs_aws_ec2
277
352
  def testSpotAutoScaleBalancingTypes(self):
278
353
  self.instanceTypes = ["m5.large/m5a.large:%f" % self.spotBid]
279
- self.numWorkers = ['2']
354
+ self.numWorkers = ["2"]
280
355
  self._test(preemptibleJobs=True)
281
356
 
282
357
 
@@ -285,26 +360,36 @@ class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
285
360
  @pytest.mark.timeout(2400)
286
361
  class AWSStaticAutoscaleTest(AWSAutoscaleTest):
287
362
  """Runs the tests on a statically provisioned cluster with autoscaling enabled."""
363
+
288
364
  def __init__(self, name):
289
365
  super().__init__(name)
290
366
  self.requestedNodeStorage = 20
291
367
 
292
368
  def launchCluster(self):
293
- from boto.ec2.blockdevicemapping import BlockDeviceType
294
-
295
369
  from toil.lib.ec2 import wait_instances_running
296
- self.createClusterUtil(args=['--leaderStorage', str(self.requestedLeaderStorage),
297
- '--nodeTypes', ",".join(self.instanceTypes),
298
- '-w', ",".join(self.numWorkers),
299
- '--nodeStorage', str(self.requestedLeaderStorage)])
300
370
 
301
- 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
+ )
302
387
  # We need to wait a little bit here because the workers might not be
303
388
  # visible to EC2 read requests immediately after the create returns,
304
389
  # which is the last thing that starting the cluster does.
305
390
  time.sleep(10)
306
- nodes = self.cluster._get_nodes_in_cluster()
307
- nodes.sort(key=lambda x: x.launch_time)
391
+ nodes: list["InstanceTypeDef"] = self.cluster._get_nodes_in_cluster_boto3()
392
+ nodes.sort(key=lambda x: x.get("LaunchTime"))
308
393
  # assuming that leader is first
309
394
  workers = nodes[1:]
310
395
  # test that two worker nodes were created
@@ -312,18 +397,49 @@ class AWSStaticAutoscaleTest(AWSAutoscaleTest):
312
397
  # test that workers have expected storage size
313
398
  # just use the first worker
314
399
  worker = workers[0]
315
- worker = next(wait_instances_running(self.boto2_ec2, [worker]))
316
- rootBlockDevice = worker.block_device_mapping["/dev/xvda"]
317
- self.assertTrue(isinstance(rootBlockDevice, BlockDeviceType))
318
- rootVolume = self.boto2_ec2.get_all_volumes(volume_ids=[rootBlockDevice.volume_id])[0]
319
- self.assertGreaterEqual(rootVolume.size, self.requestedNodeStorage)
400
+ boto3_ec2: "EC2Client" = self.aws.client(region=self.region, service_name="ec2")
401
+
402
+ worker: "InstanceTypeDef" = next(wait_instances_running(boto3_ec2, [worker]))
403
+
404
+ bdm: Optional[list["InstanceBlockDeviceMappingTypeDef"]] = worker.get(
405
+ "BlockDeviceMappings"
406
+ )
407
+ assert bdm is not None
408
+ root_block_device: Optional["EbsInstanceBlockDeviceTypeDef"] = None
409
+ for device in bdm:
410
+ if device["DeviceName"] == "/dev/xvda":
411
+ root_block_device = device["Ebs"]
412
+ assert root_block_device is not None
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
426
+ self.assertGreaterEqual(root_volume.get("Size"), self.requestedNodeStorage)
320
427
 
321
428
  def _runScript(self, toilOptions):
322
429
  # Autoscale even though we have static nodes
323
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
324
- '--nodeTypes=' + ",".join(self.instanceTypes),
325
- '--maxNodes=' + ",".join(self.numWorkers)])
326
- 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
+ ]
327
443
  runCommand.extend(toilOptions)
328
444
  self.sshUtil(runCommand)
329
445
 
@@ -332,26 +448,39 @@ class AWSStaticAutoscaleTest(AWSAutoscaleTest):
332
448
  @pytest.mark.timeout(1200)
333
449
  class AWSManagedAutoscaleTest(AWSAutoscaleTest):
334
450
  """Runs the tests on a self-scaling Kubernetes cluster."""
451
+
335
452
  def __init__(self, name):
336
453
  super().__init__(name)
337
454
  self.requestedNodeStorage = 20
338
455
 
339
456
  def launchCluster(self):
340
- from boto.ec2.blockdevicemapping import BlockDeviceType # noqa
341
-
342
- from toil.lib.ec2 import wait_instances_running # noqa
343
- self.createClusterUtil(args=['--leaderStorage', str(self.requestedLeaderStorage),
344
- '--nodeTypes', ",".join(self.instanceTypes),
345
- '--workers', ",".join([f'0-{c}' for c in self.numWorkers]),
346
- '--nodeStorage', str(self.requestedLeaderStorage),
347
- '--clusterType', 'kubernetes'])
348
-
349
- 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
+ )
350
475
 
351
476
  def _runScript(self, toilOptions):
352
477
  # Don't use the provisioner, and use Kubernetes instead of Mesos
353
- toilOptions.extend(['--batchSystem=kubernetes'])
354
- 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
+ ]
355
484
  runCommand.extend(toilOptions)
356
485
  self.sshUtil(runCommand)
357
486
 
@@ -362,37 +491,51 @@ class AWSManagedAutoscaleTest(AWSAutoscaleTest):
362
491
  class AWSAutoscaleTestMultipleNodeTypes(AbstractAWSAutoscaleTest):
363
492
  def __init__(self, name):
364
493
  super().__init__(name)
365
- self.clusterName = 'provisioner-test-' + str(uuid4())
494
+ self.clusterName = "provisioner-test-" + str(uuid4())
366
495
 
367
496
  def setUp(self):
368
497
  super().setUp()
369
- self.jobStore = f'aws:{self.awsRegion()}:autoscale-{uuid4()}'
498
+ self.jobStore = f"aws:{self.awsRegion()}:autoscale-{uuid4()}"
370
499
 
371
500
  def _getScript(self):
372
- sseKeyFile = os.path.join(os.getcwd(), 'keyFile')
373
- with open(sseKeyFile, 'w') as f:
374
- f.write('01234567890123456789012345678901')
375
- self.rsyncUtil(os.path.join(self._projectRootPath(), 'src/toil/test/sort/sort.py'), ':' + self.script())
376
- 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"))
377
509
  os.unlink(sseKeyFile)
378
510
 
379
511
  def _runScript(self, toilOptions):
380
512
  # Set memory requirements so that sort jobs can be run
381
513
  # on small instances, but merge jobs must be run on large
382
514
  # instances
383
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
384
- '--nodeTypes=' + ",".join(self.instanceTypes),
385
- '--maxNodes=' + ",".join(self.numWorkers)])
386
- 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
+ ]
387
530
  runCommand.extend(toilOptions)
388
- runCommand.append('--sseKey=' + self.data('keyFile'))
531
+ runCommand.append("--sseKey=" + self.data("keyFile"))
389
532
  self.sshUtil(runCommand)
390
533
 
391
534
  @integrative
392
535
  @needs_aws_ec2
393
536
  def testAutoScale(self):
394
537
  self.instanceTypes = ["t2.small", "m5a.large"]
395
- self.numWorkers = ['2', '1']
538
+ self.numWorkers = ["2", "1"]
396
539
  self._test()
397
540
 
398
541
 
@@ -401,16 +544,17 @@ class AWSAutoscaleTestMultipleNodeTypes(AbstractAWSAutoscaleTest):
401
544
  @pytest.mark.timeout(1200)
402
545
  class AWSRestartTest(AbstractAWSAutoscaleTest):
403
546
  """This test insures autoscaling works on a restarted Toil run."""
547
+
404
548
  def __init__(self, name):
405
549
  super().__init__(name)
406
- self.clusterName = 'restart-test-' + str(uuid4())
407
- self.scriptName = 'restartScript.py'
550
+ self.clusterName = "restart-test-" + str(uuid4())
551
+ self.scriptName = "restartScript.py"
408
552
 
409
553
  def setUp(self):
410
554
  super().setUp()
411
- self.instanceTypes = ['t2.small']
412
- self.numWorkers = ['1']
413
- 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()}"
414
558
 
415
559
  def _getScript(self):
416
560
  def restartScript():
@@ -421,38 +565,56 @@ class AWSRestartTest(AbstractAWSAutoscaleTest):
421
565
  from toil.job import Job
422
566
 
423
567
  def f0(job):
424
- if 'FAIL' in os.environ:
425
- raise RuntimeError('failed on purpose')
568
+ if "FAIL" in os.environ:
569
+ raise RuntimeError("failed on purpose")
426
570
 
427
- if __name__ == '__main__':
571
+ if __name__ == "__main__":
428
572
  parser = ArgumentParser()
429
573
  Job.Runner.addToilOptions(parser)
430
574
  options = parser.parse_args()
431
- 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")
432
576
  Job.Runner.startToil(rootJob, options)
433
577
 
434
- script = dedent('\n'.join(getsource(restartScript).split('\n')[1:]))
578
+ script = dedent("\n".join(getsource(restartScript).split("\n")[1:]))
435
579
  self.putScript(script)
436
580
 
437
581
  def _runScript(self, toilOptions):
438
582
  # Use the provisioner in the workflow
439
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
440
- '--nodeTypes=' + ",".join(self.instanceTypes),
441
- '--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
+ )
442
591
  # clean = onSuccess
443
- disallowedOptions = ['--clean=always', '--retryCount=2']
444
- 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
+ ]
445
596
  try:
446
597
  # include a default memory - on restart the minimum memory requirement is the default, usually 2 GB
447
- 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
+ ]
448
605
  command.extend(newOptions)
449
606
  self.sshUtil(command)
450
607
  except subprocess.CalledProcessError:
451
608
  pass
452
609
  else:
453
- self.fail('Command succeeded when we expected failure')
610
+ self.fail("Command succeeded when we expected failure")
454
611
  with timeLimit(600):
455
- command = [self.python(), self.script(), '--restart', '--defaultMemory=50000000']
612
+ command = [
613
+ self.python(),
614
+ self.script(),
615
+ "--restart",
616
+ "--defaultMemory=50000000",
617
+ ]
456
618
  command.extend(toilOptions)
457
619
  self.sshUtil(command)
458
620
 
@@ -466,14 +628,17 @@ class AWSRestartTest(AbstractAWSAutoscaleTest):
466
628
  class PreemptibleDeficitCompensationTest(AbstractAWSAutoscaleTest):
467
629
  def __init__(self, name):
468
630
  super().__init__(name)
469
- self.clusterName = 'deficit-test-' + str(uuid4())
470
- self.scriptName = 'userScript.py'
631
+ self.clusterName = "deficit-test-" + str(uuid4())
632
+ self.scriptName = "userScript.py"
471
633
 
472
634
  def setUp(self):
473
635
  super().setUp()
474
- self.instanceTypes = ['m5a.large:0.01', "m5a.large"] # instance needs to be available on the spot market
475
- self.numWorkers = ['1', '1']
476
- 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()}"
477
642
 
478
643
  def test(self):
479
644
  self._test(preemptibleJobs=True)
@@ -490,10 +655,10 @@ class PreemptibleDeficitCompensationTest(AbstractAWSAutoscaleTest):
490
655
  # we will observe a deficit of preemptible nodes that the non-preemptible scaler will
491
656
  # compensate for by spinning up non-preemptible nodes instead.
492
657
  #
493
- def job(job, disk='10M', cores=1, memory='10M', preemptible=True):
658
+ def job(job, disk="10M", cores=1, memory="10M", preemptible=True):
494
659
  pass
495
660
 
496
- if __name__ == '__main__':
661
+ if __name__ == "__main__":
497
662
  options = Job.Runner.getDefaultArgumentParser().parse_args()
498
663
  with Toil(options) as toil:
499
664
  if toil.config.restart:
@@ -501,14 +666,19 @@ class PreemptibleDeficitCompensationTest(AbstractAWSAutoscaleTest):
501
666
  else:
502
667
  toil.start(Job.wrapJobFn(job))
503
668
 
504
- script = dedent('\n'.join(getsource(userScript).split('\n')[1:]))
669
+ script = dedent("\n".join(getsource(userScript).split("\n")[1:]))
505
670
  self.putScript(script)
506
671
 
507
672
  def _runScript(self, toilOptions):
508
- toilOptions.extend(['--provisioner=aws', '--batchSystem=mesos',
509
- '--nodeTypes=' + ",".join(self.instanceTypes),
510
- '--maxNodes=' + ",".join(self.numWorkers)])
511
- 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"])
512
682
  command = [self.python(), self.script()]
513
683
  command.extend(toilOptions)
514
684
  self.sshUtil(command)