dstack 0.18.44__py3-none-any.whl → 0.19.0rc1__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 (267) hide show
  1. dstack/_internal/cli/commands/gateway.py +15 -3
  2. dstack/_internal/cli/commands/logs.py +0 -22
  3. dstack/_internal/cli/commands/stats.py +8 -17
  4. dstack/_internal/cli/main.py +1 -5
  5. dstack/_internal/cli/services/configurators/fleet.py +4 -39
  6. dstack/_internal/cli/services/configurators/run.py +22 -21
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/core/backends/__init__.py +56 -39
  10. dstack/_internal/core/backends/aws/__init__.py +0 -25
  11. dstack/_internal/core/backends/aws/auth.py +1 -10
  12. dstack/_internal/core/backends/aws/backend.py +26 -0
  13. dstack/_internal/core/backends/aws/compute.py +20 -45
  14. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  15. dstack/_internal/core/backends/aws/models.py +135 -0
  16. dstack/_internal/core/backends/aws/resources.py +1 -1
  17. dstack/_internal/core/backends/azure/__init__.py +0 -20
  18. dstack/_internal/core/backends/azure/auth.py +2 -11
  19. dstack/_internal/core/backends/azure/backend.py +21 -0
  20. dstack/_internal/core/backends/azure/compute.py +13 -27
  21. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  22. dstack/_internal/core/backends/azure/models.py +89 -0
  23. dstack/_internal/core/backends/base/__init__.py +0 -12
  24. dstack/_internal/core/backends/base/backend.py +18 -0
  25. dstack/_internal/core/backends/base/compute.py +153 -33
  26. dstack/_internal/core/backends/base/configurator.py +105 -0
  27. dstack/_internal/core/backends/base/models.py +14 -0
  28. dstack/_internal/core/backends/configurators.py +138 -0
  29. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  30. dstack/_internal/core/backends/cudo/backend.py +16 -0
  31. dstack/_internal/core/backends/cudo/compute.py +8 -26
  32. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  33. dstack/_internal/core/backends/cudo/models.py +37 -0
  34. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  35. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  36. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  37. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  38. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  39. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  40. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  41. dstack/_internal/core/backends/gcp/auth.py +2 -11
  42. dstack/_internal/core/backends/gcp/backend.py +17 -0
  43. dstack/_internal/core/backends/gcp/compute.py +13 -43
  44. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  45. dstack/_internal/core/backends/gcp/models.py +125 -0
  46. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  47. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  48. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  49. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  50. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  51. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  52. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  53. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  54. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  55. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  56. dstack/_internal/core/backends/local/__init__.py +0 -13
  57. dstack/_internal/core/backends/local/backend.py +14 -0
  58. dstack/_internal/core/backends/local/compute.py +16 -2
  59. dstack/_internal/core/backends/models.py +128 -0
  60. dstack/_internal/core/backends/oci/__init__.py +0 -15
  61. dstack/_internal/core/backends/oci/auth.py +1 -5
  62. dstack/_internal/core/backends/oci/backend.py +16 -0
  63. dstack/_internal/core/backends/oci/compute.py +9 -23
  64. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  65. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  66. dstack/_internal/core/backends/oci/region.py +1 -1
  67. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  68. dstack/_internal/core/backends/runpod/backend.py +16 -0
  69. dstack/_internal/core/backends/runpod/compute.py +7 -3
  70. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  71. dstack/_internal/core/backends/runpod/models.py +54 -0
  72. dstack/_internal/core/backends/template/__init__.py +0 -0
  73. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  74. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  75. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  76. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  77. dstack/_internal/core/backends/tensordock/models.py +38 -0
  78. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  79. dstack/_internal/core/backends/vastai/backend.py +16 -0
  80. dstack/_internal/core/backends/vastai/compute.py +2 -2
  81. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  82. dstack/_internal/core/backends/vastai/models.py +37 -0
  83. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  84. dstack/_internal/core/backends/vultr/backend.py +16 -0
  85. dstack/_internal/core/backends/vultr/compute.py +10 -24
  86. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  87. dstack/_internal/core/backends/vultr/models.py +34 -0
  88. dstack/_internal/core/models/backends/__init__.py +0 -184
  89. dstack/_internal/core/models/backends/base.py +0 -19
  90. dstack/_internal/core/models/configurations.py +20 -15
  91. dstack/_internal/core/models/envs.py +4 -3
  92. dstack/_internal/core/models/fleets.py +17 -22
  93. dstack/_internal/core/models/gateways.py +3 -3
  94. dstack/_internal/core/models/instances.py +24 -0
  95. dstack/_internal/core/models/profiles.py +41 -46
  96. dstack/_internal/core/models/projects.py +1 -1
  97. dstack/_internal/core/models/repos/base.py +0 -5
  98. dstack/_internal/core/models/repos/local.py +3 -3
  99. dstack/_internal/core/models/repos/remote.py +26 -12
  100. dstack/_internal/core/models/repos/virtual.py +1 -1
  101. dstack/_internal/core/models/resources.py +45 -76
  102. dstack/_internal/core/models/runs.py +17 -19
  103. dstack/_internal/core/models/volumes.py +1 -3
  104. dstack/_internal/core/services/profiles.py +7 -16
  105. dstack/_internal/core/services/repos.py +0 -4
  106. dstack/_internal/server/app.py +0 -3
  107. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  108. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  109. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  110. dstack/_internal/server/background/tasks/process_placement_groups.py +4 -1
  111. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_running_jobs.py +14 -5
  113. dstack/_internal/server/background/tasks/process_submitted_jobs.py +16 -37
  114. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  115. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  116. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  117. dstack/_internal/server/models.py +48 -9
  118. dstack/_internal/server/routers/backends.py +14 -23
  119. dstack/_internal/server/routers/instances.py +3 -4
  120. dstack/_internal/server/routers/metrics.py +10 -8
  121. dstack/_internal/server/routers/prometheus.py +1 -1
  122. dstack/_internal/server/routers/repos.py +1 -2
  123. dstack/_internal/server/routers/runs.py +13 -59
  124. dstack/_internal/server/schemas/gateways.py +14 -23
  125. dstack/_internal/server/schemas/projects.py +7 -2
  126. dstack/_internal/server/schemas/repos.py +2 -38
  127. dstack/_internal/server/schemas/runner.py +1 -0
  128. dstack/_internal/server/schemas/runs.py +1 -24
  129. dstack/_internal/server/services/backends/__init__.py +85 -158
  130. dstack/_internal/server/services/config.py +52 -576
  131. dstack/_internal/server/services/fleets.py +8 -103
  132. dstack/_internal/server/services/gateways/__init__.py +12 -4
  133. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  134. dstack/_internal/server/services/jobs/__init__.py +9 -6
  135. dstack/_internal/server/services/jobs/configurators/base.py +16 -0
  136. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  137. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  138. dstack/_internal/server/services/metrics.py +39 -13
  139. dstack/_internal/server/services/offers.py +1 -1
  140. dstack/_internal/server/services/projects.py +23 -14
  141. dstack/_internal/server/services/prometheus.py +176 -18
  142. dstack/_internal/server/services/runs.py +24 -16
  143. dstack/_internal/server/services/volumes.py +8 -4
  144. dstack/_internal/server/statics/index.html +1 -1
  145. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js → main-4fd5a4770eff59325ee3.js} +7 -7
  146. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js.map → main-4fd5a4770eff59325ee3.js.map} +1 -1
  147. dstack/_internal/server/testing/common.py +58 -32
  148. dstack/_internal/utils/json_schema.py +6 -0
  149. dstack/_internal/utils/ssh.py +2 -1
  150. dstack/api/__init__.py +4 -0
  151. dstack/api/_public/__init__.py +16 -20
  152. dstack/api/_public/backends.py +1 -1
  153. dstack/api/_public/repos.py +36 -36
  154. dstack/api/_public/runs.py +167 -83
  155. dstack/api/server/__init__.py +11 -13
  156. dstack/api/server/_backends.py +12 -16
  157. dstack/api/server/_fleets.py +15 -57
  158. dstack/api/server/_gateways.py +3 -14
  159. dstack/api/server/_repos.py +1 -4
  160. dstack/api/server/_runs.py +21 -100
  161. dstack/api/server/_volumes.py +10 -5
  162. dstack/version.py +1 -1
  163. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/METADATA +1 -1
  164. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/RECORD +218 -204
  165. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  166. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  167. tests/_internal/core/backends/aws/test_resources.py +1 -1
  168. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  169. tests/_internal/core/backends/cudo/__init__.py +0 -0
  170. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  171. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  172. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  173. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  174. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  175. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  176. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  177. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  178. tests/_internal/core/backends/runpod/__init__.py +0 -0
  179. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  180. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  181. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  182. tests/_internal/core/backends/vastai/__init__.py +0 -0
  183. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  184. tests/_internal/core/backends/vultr/__init__.py +0 -0
  185. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  186. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  187. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  188. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  189. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  190. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +0 -3
  191. tests/_internal/server/background/tasks/test_process_running_jobs.py +0 -21
  192. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  193. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  194. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  195. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  196. tests/_internal/server/routers/test_backends.py +6 -764
  197. tests/_internal/server/routers/test_fleets.py +0 -26
  198. tests/_internal/server/routers/test_gateways.py +27 -3
  199. tests/_internal/server/routers/test_instances.py +0 -10
  200. tests/_internal/server/routers/test_metrics.py +27 -0
  201. tests/_internal/server/routers/test_projects.py +56 -0
  202. tests/_internal/server/routers/test_prometheus.py +116 -27
  203. tests/_internal/server/routers/test_repos.py +0 -15
  204. tests/_internal/server/routers/test_runs.py +4 -219
  205. tests/_internal/server/routers/test_volumes.py +2 -3
  206. tests/_internal/server/services/backends/__init__.py +0 -0
  207. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  208. tests/_internal/server/services/test_config.py +7 -4
  209. tests/_internal/server/services/test_fleets.py +1 -4
  210. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  211. tests/_internal/server/services/test_metrics.py +9 -5
  212. tests/_internal/server/services/test_repos.py +1 -14
  213. tests/_internal/server/services/test_runs.py +0 -4
  214. dstack/_internal/cli/commands/pool.py +0 -581
  215. dstack/_internal/cli/commands/run.py +0 -75
  216. dstack/_internal/core/backends/aws/config.py +0 -18
  217. dstack/_internal/core/backends/azure/config.py +0 -12
  218. dstack/_internal/core/backends/base/config.py +0 -5
  219. dstack/_internal/core/backends/cudo/config.py +0 -9
  220. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  221. dstack/_internal/core/backends/gcp/config.py +0 -22
  222. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  223. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  224. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  225. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  226. dstack/_internal/core/backends/nebius/compute.py +0 -220
  227. dstack/_internal/core/backends/nebius/config.py +0 -6
  228. dstack/_internal/core/backends/nebius/types.py +0 -37
  229. dstack/_internal/core/backends/oci/config.py +0 -6
  230. dstack/_internal/core/backends/runpod/config.py +0 -17
  231. dstack/_internal/core/backends/tensordock/config.py +0 -9
  232. dstack/_internal/core/backends/vastai/config.py +0 -6
  233. dstack/_internal/core/backends/vultr/config.py +0 -9
  234. dstack/_internal/core/models/backends/aws.py +0 -86
  235. dstack/_internal/core/models/backends/azure.py +0 -68
  236. dstack/_internal/core/models/backends/cudo.py +0 -43
  237. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  238. dstack/_internal/core/models/backends/gcp.py +0 -67
  239. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  240. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  241. dstack/_internal/core/models/backends/nebius.py +0 -54
  242. dstack/_internal/core/models/backends/runpod.py +0 -42
  243. dstack/_internal/core/models/backends/tensordock.py +0 -44
  244. dstack/_internal/core/models/backends/vastai.py +0 -43
  245. dstack/_internal/core/models/backends/vultr.py +0 -40
  246. dstack/_internal/core/models/pools.py +0 -43
  247. dstack/_internal/server/routers/pools.py +0 -142
  248. dstack/_internal/server/schemas/pools.py +0 -38
  249. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  250. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  251. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  252. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  253. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  254. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  255. dstack/_internal/server/services/backends/configurators/runpod.py +0 -67
  256. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  257. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  258. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  259. dstack/api/_public/pools.py +0 -41
  260. dstack/api/_public/resources.py +0 -105
  261. dstack/api/server/_pools.py +0 -63
  262. tests/_internal/server/routers/test_pools.py +0 -612
  263. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  264. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/LICENSE.md +0 -0
  265. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/WHEEL +0 -0
  266. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/entry_points.txt +0 -0
  267. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/top_level.txt +0 -0
@@ -8,20 +8,25 @@ from pydantic import ValidationError
8
8
 
9
9
  import dstack._internal.core.backends.aws.resources as aws_resources
10
10
  from dstack._internal import settings
11
- from dstack._internal.core.backends.aws.config import AWSConfig
11
+ from dstack._internal.core.backends.aws.models import AWSAccessKeyCreds, AWSConfig
12
12
  from dstack._internal.core.backends.base.compute import (
13
13
  Compute,
14
+ ComputeWithCreateInstanceSupport,
15
+ ComputeWithGatewaySupport,
16
+ ComputeWithMultinodeSupport,
17
+ ComputeWithPlacementGroupSupport,
18
+ ComputeWithPrivateGatewaySupport,
19
+ ComputeWithReservationSupport,
20
+ ComputeWithVolumeSupport,
14
21
  generate_unique_gateway_instance_name,
15
22
  generate_unique_instance_name,
16
23
  generate_unique_volume_name,
17
24
  get_gateway_user_data,
18
- get_job_instance_name,
19
25
  get_user_data,
20
26
  merge_tags,
21
27
  )
22
28
  from dstack._internal.core.backends.base.offers import get_catalog_offers
23
29
  from dstack._internal.core.errors import ComputeError, NoCapacityError, PlacementGroupInUseError
24
- from dstack._internal.core.models.backends.aws import AWSAccessKeyCreds
25
30
  from dstack._internal.core.models.backends.base import BackendType
26
31
  from dstack._internal.core.models.common import CoreModel, is_core_model_instance
27
32
  from dstack._internal.core.models.gateways import (
@@ -33,11 +38,10 @@ from dstack._internal.core.models.instances import (
33
38
  InstanceConfiguration,
34
39
  InstanceOffer,
35
40
  InstanceOfferWithAvailability,
36
- SSHKey,
37
41
  )
38
42
  from dstack._internal.core.models.placement import PlacementGroup, PlacementGroupProvisioningData
39
43
  from dstack._internal.core.models.resources import Memory, Range
40
- from dstack._internal.core.models.runs import Job, JobProvisioningData, Requirements, Run
44
+ from dstack._internal.core.models.runs import JobProvisioningData, Requirements
41
45
  from dstack._internal.core.models.volumes import (
42
46
  Volume,
43
47
  VolumeAttachmentData,
@@ -62,7 +66,16 @@ class AWSVolumeBackendData(CoreModel):
62
66
  iops: int
63
67
 
64
68
 
65
- class AWSCompute(Compute):
69
+ class AWSCompute(
70
+ ComputeWithCreateInstanceSupport,
71
+ ComputeWithMultinodeSupport,
72
+ ComputeWithReservationSupport,
73
+ ComputeWithPlacementGroupSupport,
74
+ ComputeWithGatewaySupport,
75
+ ComputeWithPrivateGatewaySupport,
76
+ ComputeWithVolumeSupport,
77
+ Compute,
78
+ ):
66
79
  def __init__(self, config: AWSConfig):
67
80
  super().__init__()
68
81
  self.config = config
@@ -270,44 +283,6 @@ class AWSCompute(Compute):
270
283
  continue
271
284
  raise NoCapacityError()
272
285
 
273
- def run_job(
274
- self,
275
- run: Run,
276
- job: Job,
277
- instance_offer: InstanceOfferWithAvailability,
278
- project_ssh_public_key: str,
279
- project_ssh_private_key: str,
280
- volumes: List[Volume],
281
- ) -> JobProvisioningData:
282
- # TODO: run_job is the same for vm-based backends, refactor
283
- instance_config = InstanceConfiguration(
284
- project_name=run.project_name,
285
- instance_name=get_job_instance_name(run, job), # TODO: generate name
286
- ssh_keys=[
287
- SSHKey(public=project_ssh_public_key.strip()),
288
- ],
289
- user=run.user,
290
- volumes=volumes,
291
- reservation=run.run_spec.configuration.reservation,
292
- )
293
- instance_offer = instance_offer.copy()
294
- if len(volumes) > 0:
295
- volume = volumes[0]
296
- if (
297
- volume.provisioning_data is not None
298
- and volume.provisioning_data.availability_zone is not None
299
- ):
300
- if instance_offer.availability_zones is None:
301
- instance_offer.availability_zones = [
302
- volume.provisioning_data.availability_zone
303
- ]
304
- instance_offer.availability_zones = [
305
- z
306
- for z in instance_offer.availability_zones
307
- if z == volume.provisioning_data.availability_zone
308
- ]
309
- return self.create_instance(instance_offer, instance_config)
310
-
311
286
  def create_placement_group(
312
287
  self,
313
288
  placement_group: PlacementGroup,
@@ -382,7 +357,7 @@ class AWSCompute(Compute):
382
357
  **aws_resources.create_instances_struct(
383
358
  disk_size=10,
384
359
  image_id=aws_resources.get_gateway_image_id(ec2_client),
385
- instance_type="t2.micro",
360
+ instance_type="t3.micro",
386
361
  iam_instance_profile=None,
387
362
  user_data=get_gateway_user_data(configuration.ssh_key_pub),
388
363
  tags=tags,
@@ -1,41 +1,35 @@
1
1
  import concurrent.futures
2
2
  import json
3
- from typing import List
4
3
 
5
4
  import botocore.exceptions
6
5
  from boto3.session import Session
7
6
 
8
- from dstack._internal.core.backends.aws import AWSBackend, auth, compute, resources
9
- from dstack._internal.core.backends.aws.config import AWSConfig
10
- from dstack._internal.core.errors import (
11
- BackendAuthError,
12
- BackendError,
13
- ServerClientError,
14
- )
15
- from dstack._internal.core.models.backends.aws import (
16
- AnyAWSConfigInfo,
7
+ from dstack._internal.core.backends.aws import auth, compute, resources
8
+ from dstack._internal.core.backends.aws.backend import AWSBackend
9
+ from dstack._internal.core.backends.aws.models import (
10
+ AnyAWSBackendConfig,
17
11
  AWSAccessKeyCreds,
18
- AWSConfigInfo,
19
- AWSConfigInfoWithCreds,
20
- AWSConfigInfoWithCredsPartial,
21
- AWSConfigValues,
12
+ AWSBackendConfig,
13
+ AWSBackendConfigWithCreds,
14
+ AWSConfig,
22
15
  AWSCreds,
23
16
  AWSDefaultCreds,
24
17
  AWSStoredConfig,
25
18
  )
26
- from dstack._internal.core.models.backends.base import (
27
- BackendType,
28
- ConfigElementValue,
29
- ConfigMultiElement,
30
- )
31
- from dstack._internal.core.models.common import is_core_model_instance
32
- from dstack._internal.server import settings
33
- from dstack._internal.server.models import BackendModel, DecryptedString, ProjectModel
34
- from dstack._internal.server.services.backends.configurators.base import (
19
+ from dstack._internal.core.backends.base.configurator import (
35
20
  TAGS_MAX_NUM,
21
+ BackendRecord,
36
22
  Configurator,
37
23
  raise_invalid_credentials_error,
38
24
  )
25
+ from dstack._internal.core.errors import (
26
+ BackendError,
27
+ ServerClientError,
28
+ )
29
+ from dstack._internal.core.models.backends.base import (
30
+ BackendType,
31
+ )
32
+ from dstack._internal.core.models.common import is_core_model_instance
39
33
  from dstack._internal.utils.logging import get_logger
40
34
 
41
35
  logger = get_logger(__name__)
@@ -59,33 +53,11 @@ MAIN_REGION = "us-east-1"
59
53
 
60
54
 
61
55
  class AWSConfigurator(Configurator):
62
- TYPE: BackendType = BackendType.AWS
56
+ TYPE = BackendType.AWS
57
+ BACKEND_CLASS = AWSBackend
63
58
 
64
- def get_default_configs(self) -> List[AWSConfigInfoWithCreds]:
65
- if not auth.default_creds_available():
66
- return []
67
- try:
68
- auth.authenticate(creds=AWSDefaultCreds(), region=MAIN_REGION)
69
- except BackendAuthError:
70
- return []
71
- return [
72
- AWSConfigInfoWithCreds(
73
- regions=DEFAULT_REGIONS,
74
- creds=AWSDefaultCreds(),
75
- )
76
- ]
77
-
78
- def get_config_values(self, config: AWSConfigInfoWithCredsPartial) -> AWSConfigValues:
79
- config_values = AWSConfigValues(regions=None)
80
- config_values.default_creds = (
81
- settings.DEFAULT_CREDS_ENABLED and auth.default_creds_available()
82
- )
83
- if config.creds is None:
84
- return config_values
85
- if (
86
- is_core_model_instance(config.creds, AWSDefaultCreds)
87
- and not settings.DEFAULT_CREDS_ENABLED
88
- ):
59
+ def validate_config(self, config: AWSBackendConfigWithCreds, default_creds_enabled: bool):
60
+ if is_core_model_instance(config.creds, AWSDefaultCreds) and not default_creds_enabled:
89
61
  raise_invalid_credentials_error(fields=[["creds"]])
90
62
  try:
91
63
  session = auth.authenticate(creds=config.creds, region=MAIN_REGION)
@@ -99,52 +71,41 @@ class AWSConfigurator(Configurator):
99
71
  )
100
72
  else:
101
73
  raise_invalid_credentials_error(fields=[["creds"]])
102
- config_values.regions = self._get_regions_element(
103
- selected=config.regions or DEFAULT_REGIONS
104
- )
105
- self._check_config(session=session, config=config)
106
- return config_values
74
+ self._check_config_tags(config)
75
+ self._check_config_iam_instance_profile(session, config)
76
+ self._check_config_vpc(session, config)
107
77
 
108
78
  def create_backend(
109
- self, project: ProjectModel, config: AWSConfigInfoWithCreds
110
- ) -> BackendModel:
79
+ self, project_name: str, config: AWSBackendConfigWithCreds
80
+ ) -> BackendRecord:
111
81
  if config.regions is None:
112
82
  config.regions = DEFAULT_REGIONS
113
- return BackendModel(
114
- project_id=project.id,
115
- type=self.TYPE.value,
116
- config=AWSStoredConfig(**AWSConfigInfo.__response__.parse_obj(config).dict()).json(),
117
- auth=DecryptedString(plaintext=AWSCreds.parse_obj(config.creds).json()),
83
+ return BackendRecord(
84
+ config=AWSStoredConfig(
85
+ **AWSBackendConfig.__response__.parse_obj(config).dict()
86
+ ).json(),
87
+ auth=AWSCreds.parse_obj(config.creds).json(),
118
88
  )
119
89
 
120
- def get_config_info(self, model: BackendModel, include_creds: bool) -> AnyAWSConfigInfo:
121
- config = self._get_backend_config(model)
90
+ def get_backend_config(
91
+ self, record: BackendRecord, include_creds: bool
92
+ ) -> AnyAWSBackendConfig:
93
+ config = self._get_config(record)
122
94
  if include_creds:
123
- return AWSConfigInfoWithCreds.__response__.parse_obj(config)
124
- return AWSConfigInfo.__response__.parse_obj(config)
95
+ return AWSBackendConfigWithCreds.__response__.parse_obj(config)
96
+ return AWSBackendConfig.__response__.parse_obj(config)
125
97
 
126
- def get_backend(self, model: BackendModel) -> AWSBackend:
127
- config = self._get_backend_config(model)
98
+ def get_backend(self, record: BackendRecord) -> AWSBackend:
99
+ config = self._get_config(record)
128
100
  return AWSBackend(config=config)
129
101
 
130
- def _get_backend_config(self, model: BackendModel) -> AWSConfig:
102
+ def _get_config(self, record: BackendRecord) -> AWSConfig:
131
103
  return AWSConfig.__response__(
132
- **json.loads(model.config),
133
- creds=AWSCreds.parse_raw(model.auth.get_plaintext_or_error()).__root__,
104
+ **json.loads(record.config),
105
+ creds=AWSCreds.parse_raw(record.auth).__root__,
134
106
  )
135
107
 
136
- def _get_regions_element(self, selected: List[str]) -> ConfigMultiElement:
137
- element = ConfigMultiElement(selected=selected)
138
- for r in REGION_VALUES:
139
- element.values.append(ConfigElementValue(value=r, label=r))
140
- return element
141
-
142
- def _check_config(self, session: Session, config: AWSConfigInfoWithCredsPartial):
143
- self._check_tags_config(config)
144
- self._check_iam_instance_profile_config(session, config)
145
- self._check_vpc_config(session, config)
146
-
147
- def _check_tags_config(self, config: AWSConfigInfoWithCredsPartial):
108
+ def _check_config_tags(self, config: AWSBackendConfigWithCreds):
148
109
  if not config.tags:
149
110
  return
150
111
  if len(config.tags) > TAGS_MAX_NUM:
@@ -156,8 +117,8 @@ class AWSConfigurator(Configurator):
156
117
  except BackendError as e:
157
118
  raise ServerClientError(e.args[0])
158
119
 
159
- def _check_iam_instance_profile_config(
160
- self, session: Session, config: AWSConfigInfoWithCredsPartial
120
+ def _check_config_iam_instance_profile(
121
+ self, session: Session, config: AWSBackendConfigWithCreds
161
122
  ):
162
123
  if config.iam_instance_profile is None:
163
124
  return
@@ -181,7 +142,7 @@ class AWSConfigurator(Configurator):
181
142
  f"Failed to check IAM instance profile {config.iam_instance_profile}"
182
143
  )
183
144
 
184
- def _check_vpc_config(self, session: Session, config: AWSConfigInfoWithCredsPartial):
145
+ def _check_config_vpc(self, session: Session, config: AWSBackendConfigWithCreds):
185
146
  allocate_public_ip = config.public_ips if config.public_ips is not None else True
186
147
  use_default_vpcs = config.default_vpcs if config.default_vpcs is not None else True
187
148
  if config.vpc_name is not None and config.vpc_ids is not None:
@@ -0,0 +1,135 @@
1
+ from typing import Annotated, Dict, List, Literal, Optional, Union
2
+
3
+ from pydantic import Field
4
+
5
+ from dstack._internal.core.models.common import CoreModel
6
+
7
+
8
+ class AWSOSImage(CoreModel):
9
+ name: Annotated[str, Field(description="The AMI name")]
10
+ owner: Annotated[
11
+ str,
12
+ Field(regex=r"^(\d{12}|self)$", description="The AMI owner, account ID or `self`"),
13
+ ] = "self"
14
+ user: Annotated[str, Field(description="The OS user for provisioning")]
15
+
16
+
17
+ class AWSOSImageConfig(CoreModel):
18
+ cpu: Annotated[Optional[AWSOSImage], Field(description="The AMI used for CPU instances")] = (
19
+ None
20
+ )
21
+ nvidia: Annotated[
22
+ Optional[AWSOSImage], Field(description="The AMI used for NVIDIA GPU instances")
23
+ ] = None
24
+
25
+
26
+ class AWSAccessKeyCreds(CoreModel):
27
+ type: Annotated[Literal["access_key"], Field(description="The type of credentials")] = (
28
+ "access_key"
29
+ )
30
+ access_key: Annotated[str, Field(description="The access key")]
31
+ secret_key: Annotated[str, Field(description="The secret key")]
32
+
33
+
34
+ class AWSDefaultCreds(CoreModel):
35
+ type: Annotated[Literal["default"], Field(description="The type of credentials")] = "default"
36
+
37
+
38
+ AnyAWSCreds = Union[AWSAccessKeyCreds, AWSDefaultCreds]
39
+
40
+
41
+ class AWSCreds(CoreModel):
42
+ __root__: AnyAWSCreds = Field(..., discriminator="type")
43
+
44
+
45
+ class AWSBackendConfig(CoreModel):
46
+ type: Annotated[Literal["aws"], Field(description="The type of the backend")] = "aws"
47
+ regions: Annotated[
48
+ Optional[List[str]], Field(description="The list of AWS regions. Omit to use all regions")
49
+ ] = None
50
+ vpc_name: Annotated[
51
+ Optional[str],
52
+ Field(
53
+ description=(
54
+ "The name of custom VPCs. All configured regions must have a VPC with this name."
55
+ " If your custom VPCs don't have names or have different names in different regions, use `vpc_ids` instead."
56
+ )
57
+ ),
58
+ ] = None
59
+ vpc_ids: Annotated[
60
+ Optional[Dict[str, str]],
61
+ Field(
62
+ description=(
63
+ "The mapping from AWS regions to VPC IDs."
64
+ " If `default_vpcs: true`, omitted regions will use default VPCs"
65
+ )
66
+ ),
67
+ ] = None
68
+ default_vpcs: Annotated[
69
+ Optional[bool],
70
+ Field(
71
+ description=(
72
+ "A flag to enable/disable using default VPCs in regions not configured by `vpc_ids`."
73
+ " Set to `false` if default VPCs should never be used."
74
+ " Defaults to `true`"
75
+ )
76
+ ),
77
+ ] = None
78
+ public_ips: Annotated[
79
+ Optional[bool],
80
+ Field(
81
+ description=(
82
+ "A flag to enable/disable public IP assigning on instances."
83
+ " `public_ips: false` requires at least one private subnet with outbound internet connectivity"
84
+ " provided by a NAT Gateway or a Transit Gateway."
85
+ " Defaults to `true`"
86
+ )
87
+ ),
88
+ ] = None
89
+ iam_instance_profile: Annotated[
90
+ Optional[str],
91
+ Field(
92
+ description=(
93
+ "The name of the IAM instance profile to associate with EC2 instances."
94
+ " You can also specify the IAM role name for roles created via the AWS console."
95
+ " AWS automatically creates an instance profile and gives it the same name as the role"
96
+ )
97
+ ),
98
+ ] = None
99
+ tags: Annotated[
100
+ Optional[Dict[str, str]],
101
+ Field(description="The tags that will be assigned to resources created by `dstack`"),
102
+ ] = None
103
+ os_images: Annotated[
104
+ Optional[AWSOSImageConfig],
105
+ Field(
106
+ description="The mapping of instance categories (CPU, NVIDIA GPU) to AMI configurations"
107
+ ),
108
+ ] = None
109
+
110
+
111
+ class AWSBackendConfigWithCreds(AWSBackendConfig):
112
+ creds: AnyAWSCreds = Field(..., description="The credentials", discriminator="type")
113
+
114
+
115
+ AnyAWSBackendConfig = Union[AWSBackendConfig, AWSBackendConfigWithCreds]
116
+
117
+
118
+ class AWSStoredConfig(AWSBackendConfig):
119
+ pass
120
+
121
+
122
+ class AWSConfig(AWSStoredConfig):
123
+ creds: AnyAWSCreds
124
+
125
+ @property
126
+ def allocate_public_ips(self) -> bool:
127
+ if self.public_ips is not None:
128
+ return self.public_ips
129
+ return True
130
+
131
+ @property
132
+ def use_default_vpcs(self) -> bool:
133
+ if self.default_vpcs is not None:
134
+ return self.default_vpcs
135
+ return True
@@ -5,8 +5,8 @@ import botocore.client
5
5
  import botocore.exceptions
6
6
 
7
7
  import dstack.version as version
8
+ from dstack._internal.core.backends.aws.models import AWSOSImageConfig
8
9
  from dstack._internal.core.errors import BackendError, ComputeError, ComputeResourceNotFoundError
9
- from dstack._internal.core.models.backends.aws import AWSOSImageConfig
10
10
  from dstack._internal.utils.logging import get_logger
11
11
 
12
12
  logger = get_logger(__name__)
@@ -1,20 +0,0 @@
1
- from dstack._internal.core.backends.azure import auth
2
- from dstack._internal.core.backends.azure.compute import AzureCompute
3
- from dstack._internal.core.backends.azure.config import AzureConfig
4
- from dstack._internal.core.backends.base import Backend
5
- from dstack._internal.core.models.backends.base import BackendType
6
-
7
-
8
- class AzureBackend(Backend):
9
- TYPE: BackendType = BackendType.AZURE
10
-
11
- def __init__(self, config: AzureConfig):
12
- self.config = config
13
- self.credential, _ = auth.authenticate(self.config.creds)
14
- self._compute = AzureCompute(
15
- config=self.config,
16
- credential=self.credential,
17
- )
18
-
19
- def compute(self) -> AzureCompute:
20
- return self._compute
@@ -4,12 +4,11 @@ from azure.core.exceptions import ClientAuthenticationError
4
4
  from azure.identity import ClientSecretCredential, DefaultAzureCredential
5
5
  from azure.mgmt.subscription import SubscriptionClient
6
6
 
7
- from dstack._internal.core.errors import BackendAuthError
8
- from dstack._internal.core.models.backends.azure import (
7
+ from dstack._internal.core.backends.azure.models import (
9
8
  AnyAzureCreds,
10
9
  AzureClientCreds,
11
- AzureDefaultCreds,
12
10
  )
11
+ from dstack._internal.core.errors import BackendAuthError
13
12
  from dstack._internal.core.models.common import is_core_model_instance
14
13
 
15
14
  AzureCredential = Union[ClientSecretCredential, DefaultAzureCredential]
@@ -39,11 +38,3 @@ def check_credential(credential: AzureCredential):
39
38
  list(client.subscriptions.list())
40
39
  except ClientAuthenticationError:
41
40
  raise BackendAuthError()
42
-
43
-
44
- def default_creds_available() -> bool:
45
- try:
46
- authenticate(AzureDefaultCreds())
47
- except BackendAuthError:
48
- return False
49
- return True
@@ -0,0 +1,21 @@
1
+ from dstack._internal.core.backends.azure import auth
2
+ from dstack._internal.core.backends.azure.compute import AzureCompute
3
+ from dstack._internal.core.backends.azure.models import AzureConfig
4
+ from dstack._internal.core.backends.base.backend import Backend
5
+ from dstack._internal.core.models.backends.base import BackendType
6
+
7
+
8
+ class AzureBackend(Backend):
9
+ TYPE = BackendType.AZURE
10
+ COMPUTE_CLASS = AzureCompute
11
+
12
+ def __init__(self, config: AzureConfig):
13
+ self.config = config
14
+ self.credential, _ = auth.authenticate(self.config.creds)
15
+ self._compute = AzureCompute(
16
+ config=self.config,
17
+ credential=self.credential,
18
+ )
19
+
20
+ def compute(self) -> AzureCompute:
21
+ return self._compute
@@ -36,13 +36,15 @@ from dstack import version
36
36
  from dstack._internal import settings
37
37
  from dstack._internal.core.backends.azure import resources as azure_resources
38
38
  from dstack._internal.core.backends.azure import utils as azure_utils
39
- from dstack._internal.core.backends.azure.config import AzureConfig
39
+ from dstack._internal.core.backends.azure.models import AzureConfig
40
40
  from dstack._internal.core.backends.base.compute import (
41
41
  Compute,
42
+ ComputeWithCreateInstanceSupport,
43
+ ComputeWithGatewaySupport,
44
+ ComputeWithMultinodeSupport,
42
45
  generate_unique_gateway_instance_name,
43
46
  generate_unique_instance_name,
44
47
  get_gateway_user_data,
45
- get_job_instance_name,
46
48
  get_user_data,
47
49
  merge_tags,
48
50
  )
@@ -59,11 +61,9 @@ from dstack._internal.core.models.instances import (
59
61
  InstanceOffer,
60
62
  InstanceOfferWithAvailability,
61
63
  InstanceType,
62
- SSHKey,
63
64
  )
64
65
  from dstack._internal.core.models.resources import Memory, Range
65
- from dstack._internal.core.models.runs import Job, JobProvisioningData, Requirements, Run
66
- from dstack._internal.core.models.volumes import Volume
66
+ from dstack._internal.core.models.runs import JobProvisioningData, Requirements
67
67
  from dstack._internal.utils.logging import get_logger
68
68
 
69
69
  logger = get_logger(__name__)
@@ -71,7 +71,12 @@ logger = get_logger(__name__)
71
71
  CONFIGURABLE_DISK_SIZE = Range[Memory](min=Memory.parse("30GB"), max=Memory.parse("4095GB"))
72
72
 
73
73
 
74
- class AzureCompute(Compute):
74
+ class AzureCompute(
75
+ ComputeWithCreateInstanceSupport,
76
+ ComputeWithMultinodeSupport,
77
+ ComputeWithGatewaySupport,
78
+ Compute,
79
+ ):
75
80
  def __init__(self, config: AzureConfig, credential: TokenCredential):
76
81
  super().__init__()
77
82
  self.config = config
@@ -88,14 +93,14 @@ class AzureCompute(Compute):
88
93
  ) -> List[InstanceOfferWithAvailability]:
89
94
  offers = get_catalog_offers(
90
95
  backend=BackendType.AZURE,
91
- locations=self.config.locations,
96
+ locations=self.config.regions,
92
97
  requirements=requirements,
93
98
  configurable_disk_size=CONFIGURABLE_DISK_SIZE,
94
99
  extra_filter=_supported_instances,
95
100
  )
96
101
  offers_with_availability = _get_offers_with_availability(
97
102
  compute_client=self._compute_client,
98
- config_locations=self.config.locations,
103
+ config_locations=self.config.regions,
99
104
  offers=offers,
100
105
  )
101
106
  return offers_with_availability
@@ -190,25 +195,6 @@ class AzureCompute(Compute):
190
195
  backend_data=None,
191
196
  )
192
197
 
193
- def run_job(
194
- self,
195
- run: Run,
196
- job: Job,
197
- instance_offer: InstanceOfferWithAvailability,
198
- project_ssh_public_key: str,
199
- project_ssh_private_key: str,
200
- volumes: List[Volume],
201
- ) -> JobProvisioningData:
202
- instance_config = InstanceConfiguration(
203
- project_name=run.project_name,
204
- instance_name=get_job_instance_name(run, job), # TODO: generate name
205
- ssh_keys=[
206
- SSHKey(public=project_ssh_public_key.strip()),
207
- ],
208
- user=run.user,
209
- )
210
- return self.create_instance(instance_offer, instance_config)
211
-
212
198
  def terminate_instance(
213
199
  self, instance_id: str, region: str, backend_data: Optional[str] = None
214
200
  ):