dstack 0.18.43__py3-none-any.whl → 0.19.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 (278) 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 -20
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/cli/utils/run.py +11 -0
  10. dstack/_internal/core/backends/__init__.py +56 -39
  11. dstack/_internal/core/backends/aws/__init__.py +0 -25
  12. dstack/_internal/core/backends/aws/auth.py +1 -10
  13. dstack/_internal/core/backends/aws/backend.py +26 -0
  14. dstack/_internal/core/backends/aws/compute.py +21 -45
  15. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  16. dstack/_internal/core/backends/aws/models.py +135 -0
  17. dstack/_internal/core/backends/aws/resources.py +1 -1
  18. dstack/_internal/core/backends/azure/__init__.py +0 -20
  19. dstack/_internal/core/backends/azure/auth.py +2 -11
  20. dstack/_internal/core/backends/azure/backend.py +21 -0
  21. dstack/_internal/core/backends/azure/compute.py +14 -28
  22. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  23. dstack/_internal/core/backends/azure/models.py +89 -0
  24. dstack/_internal/core/backends/base/__init__.py +0 -12
  25. dstack/_internal/core/backends/base/backend.py +18 -0
  26. dstack/_internal/core/backends/base/compute.py +153 -33
  27. dstack/_internal/core/backends/base/configurator.py +105 -0
  28. dstack/_internal/core/backends/base/models.py +14 -0
  29. dstack/_internal/core/backends/configurators.py +138 -0
  30. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  31. dstack/_internal/core/backends/cudo/backend.py +16 -0
  32. dstack/_internal/core/backends/cudo/compute.py +8 -26
  33. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  34. dstack/_internal/core/backends/cudo/models.py +37 -0
  35. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  36. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  37. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  38. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  39. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  40. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  41. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  42. dstack/_internal/core/backends/gcp/auth.py +2 -11
  43. dstack/_internal/core/backends/gcp/backend.py +17 -0
  44. dstack/_internal/core/backends/gcp/compute.py +14 -44
  45. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  46. dstack/_internal/core/backends/gcp/models.py +125 -0
  47. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  48. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  49. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  50. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  51. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  52. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  53. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  54. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  55. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  56. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  57. dstack/_internal/core/backends/local/__init__.py +0 -13
  58. dstack/_internal/core/backends/local/backend.py +14 -0
  59. dstack/_internal/core/backends/local/compute.py +16 -2
  60. dstack/_internal/core/backends/models.py +128 -0
  61. dstack/_internal/core/backends/oci/__init__.py +0 -15
  62. dstack/_internal/core/backends/oci/auth.py +1 -5
  63. dstack/_internal/core/backends/oci/backend.py +16 -0
  64. dstack/_internal/core/backends/oci/compute.py +9 -23
  65. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  66. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  67. dstack/_internal/core/backends/oci/region.py +1 -1
  68. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  69. dstack/_internal/core/backends/runpod/backend.py +16 -0
  70. dstack/_internal/core/backends/runpod/compute.py +28 -6
  71. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  72. dstack/_internal/core/backends/runpod/models.py +54 -0
  73. dstack/_internal/core/backends/template/__init__.py +0 -0
  74. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  75. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  76. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  77. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  78. dstack/_internal/core/backends/tensordock/models.py +38 -0
  79. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  80. dstack/_internal/core/backends/vastai/backend.py +16 -0
  81. dstack/_internal/core/backends/vastai/compute.py +2 -2
  82. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  83. dstack/_internal/core/backends/vastai/models.py +37 -0
  84. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  85. dstack/_internal/core/backends/vultr/backend.py +16 -0
  86. dstack/_internal/core/backends/vultr/compute.py +10 -24
  87. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  88. dstack/_internal/core/backends/vultr/models.py +34 -0
  89. dstack/_internal/core/models/backends/__init__.py +0 -184
  90. dstack/_internal/core/models/backends/base.py +0 -19
  91. dstack/_internal/core/models/configurations.py +22 -16
  92. dstack/_internal/core/models/envs.py +4 -3
  93. dstack/_internal/core/models/fleets.py +17 -22
  94. dstack/_internal/core/models/gateways.py +3 -3
  95. dstack/_internal/core/models/instances.py +24 -0
  96. dstack/_internal/core/models/profiles.py +85 -45
  97. dstack/_internal/core/models/projects.py +1 -1
  98. dstack/_internal/core/models/repos/base.py +0 -5
  99. dstack/_internal/core/models/repos/local.py +3 -3
  100. dstack/_internal/core/models/repos/remote.py +26 -12
  101. dstack/_internal/core/models/repos/virtual.py +1 -1
  102. dstack/_internal/core/models/resources.py +45 -76
  103. dstack/_internal/core/models/runs.py +21 -19
  104. dstack/_internal/core/models/volumes.py +1 -3
  105. dstack/_internal/core/services/profiles.py +7 -16
  106. dstack/_internal/core/services/repos.py +0 -4
  107. dstack/_internal/server/app.py +11 -4
  108. dstack/_internal/server/background/__init__.py +10 -0
  109. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  110. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  111. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_placement_groups.py +5 -1
  113. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +135 -0
  114. dstack/_internal/server/background/tasks/process_running_jobs.py +80 -24
  115. dstack/_internal/server/background/tasks/process_runs.py +1 -0
  116. dstack/_internal/server/background/tasks/process_submitted_jobs.py +20 -38
  117. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  118. dstack/_internal/server/migrations/versions/60e444118b6d_add_jobprometheusmetrics.py +40 -0
  119. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  120. dstack/_internal/server/migrations/versions/98d1b92988bc_add_jobterminationreason_terminated_due_.py +140 -0
  121. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  122. dstack/_internal/server/models.py +59 -9
  123. dstack/_internal/server/routers/backends.py +14 -23
  124. dstack/_internal/server/routers/instances.py +3 -4
  125. dstack/_internal/server/routers/metrics.py +31 -10
  126. dstack/_internal/server/routers/prometheus.py +36 -0
  127. dstack/_internal/server/routers/repos.py +1 -2
  128. dstack/_internal/server/routers/runs.py +13 -59
  129. dstack/_internal/server/schemas/gateways.py +14 -23
  130. dstack/_internal/server/schemas/projects.py +7 -2
  131. dstack/_internal/server/schemas/repos.py +2 -38
  132. dstack/_internal/server/schemas/runner.py +1 -0
  133. dstack/_internal/server/schemas/runs.py +1 -24
  134. dstack/_internal/server/security/permissions.py +1 -1
  135. dstack/_internal/server/services/backends/__init__.py +85 -158
  136. dstack/_internal/server/services/config.py +53 -567
  137. dstack/_internal/server/services/fleets.py +9 -103
  138. dstack/_internal/server/services/gateways/__init__.py +13 -4
  139. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  140. dstack/_internal/server/services/jobs/__init__.py +9 -6
  141. dstack/_internal/server/services/jobs/configurators/base.py +25 -1
  142. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  143. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  144. dstack/_internal/server/services/metrics.py +131 -72
  145. dstack/_internal/server/services/offers.py +1 -1
  146. dstack/_internal/server/services/projects.py +23 -14
  147. dstack/_internal/server/services/prometheus.py +245 -0
  148. dstack/_internal/server/services/runner/client.py +14 -3
  149. dstack/_internal/server/services/runs.py +67 -31
  150. dstack/_internal/server/services/volumes.py +9 -4
  151. dstack/_internal/server/settings.py +3 -0
  152. dstack/_internal/server/statics/index.html +1 -1
  153. dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js → main-4a0fe83e84574654e397.js} +76 -19
  154. dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js.map → main-4a0fe83e84574654e397.js.map} +1 -1
  155. dstack/_internal/server/statics/{main-7510e71dfa9749a4e70e.css → main-da9f8c06a69c20dac23e.css} +1 -1
  156. dstack/_internal/server/statics/static/media/entraID.d65d1f3e9486a8e56d24fc07b3230885.svg +9 -0
  157. dstack/_internal/server/testing/common.py +75 -32
  158. dstack/_internal/utils/json_schema.py +6 -0
  159. dstack/_internal/utils/ssh.py +2 -1
  160. dstack/api/__init__.py +4 -0
  161. dstack/api/_public/__init__.py +16 -20
  162. dstack/api/_public/backends.py +1 -1
  163. dstack/api/_public/repos.py +36 -36
  164. dstack/api/_public/runs.py +170 -83
  165. dstack/api/server/__init__.py +11 -13
  166. dstack/api/server/_backends.py +12 -16
  167. dstack/api/server/_fleets.py +15 -55
  168. dstack/api/server/_gateways.py +3 -14
  169. dstack/api/server/_repos.py +1 -4
  170. dstack/api/server/_runs.py +21 -96
  171. dstack/api/server/_volumes.py +10 -5
  172. dstack/api/utils.py +3 -0
  173. dstack/version.py +1 -1
  174. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/METADATA +10 -1
  175. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/RECORD +229 -206
  176. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  177. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  178. tests/_internal/core/backends/aws/test_resources.py +1 -1
  179. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  180. tests/_internal/core/backends/cudo/__init__.py +0 -0
  181. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  182. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  183. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  184. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  185. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  186. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  187. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  188. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  189. tests/_internal/core/backends/runpod/__init__.py +0 -0
  190. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  191. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  192. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  193. tests/_internal/core/backends/vastai/__init__.py +0 -0
  194. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  195. tests/_internal/core/backends/vultr/__init__.py +0 -0
  196. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  197. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  198. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  199. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  200. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  201. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +186 -0
  202. tests/_internal/server/background/tasks/test_process_running_jobs.py +123 -19
  203. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  204. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  205. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  206. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  207. tests/_internal/server/routers/test_backends.py +6 -764
  208. tests/_internal/server/routers/test_fleets.py +2 -26
  209. tests/_internal/server/routers/test_gateways.py +27 -3
  210. tests/_internal/server/routers/test_instances.py +0 -10
  211. tests/_internal/server/routers/test_metrics.py +42 -0
  212. tests/_internal/server/routers/test_projects.py +56 -0
  213. tests/_internal/server/routers/test_prometheus.py +333 -0
  214. tests/_internal/server/routers/test_repos.py +0 -15
  215. tests/_internal/server/routers/test_runs.py +83 -275
  216. tests/_internal/server/routers/test_volumes.py +2 -3
  217. tests/_internal/server/services/backends/__init__.py +0 -0
  218. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  219. tests/_internal/server/services/test_config.py +7 -4
  220. tests/_internal/server/services/test_fleets.py +1 -4
  221. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  222. tests/_internal/server/services/test_metrics.py +167 -0
  223. tests/_internal/server/services/test_repos.py +1 -14
  224. tests/_internal/server/services/test_runs.py +0 -4
  225. dstack/_internal/cli/commands/pool.py +0 -581
  226. dstack/_internal/cli/commands/run.py +0 -75
  227. dstack/_internal/core/backends/aws/config.py +0 -18
  228. dstack/_internal/core/backends/azure/config.py +0 -12
  229. dstack/_internal/core/backends/base/config.py +0 -5
  230. dstack/_internal/core/backends/cudo/config.py +0 -9
  231. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  232. dstack/_internal/core/backends/gcp/config.py +0 -22
  233. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  234. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  235. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  236. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  237. dstack/_internal/core/backends/nebius/compute.py +0 -220
  238. dstack/_internal/core/backends/nebius/config.py +0 -6
  239. dstack/_internal/core/backends/nebius/types.py +0 -37
  240. dstack/_internal/core/backends/oci/config.py +0 -6
  241. dstack/_internal/core/backends/runpod/config.py +0 -9
  242. dstack/_internal/core/backends/tensordock/config.py +0 -9
  243. dstack/_internal/core/backends/vastai/config.py +0 -6
  244. dstack/_internal/core/backends/vultr/config.py +0 -9
  245. dstack/_internal/core/models/backends/aws.py +0 -86
  246. dstack/_internal/core/models/backends/azure.py +0 -68
  247. dstack/_internal/core/models/backends/cudo.py +0 -43
  248. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  249. dstack/_internal/core/models/backends/gcp.py +0 -67
  250. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  251. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  252. dstack/_internal/core/models/backends/nebius.py +0 -54
  253. dstack/_internal/core/models/backends/runpod.py +0 -40
  254. dstack/_internal/core/models/backends/tensordock.py +0 -44
  255. dstack/_internal/core/models/backends/vastai.py +0 -43
  256. dstack/_internal/core/models/backends/vultr.py +0 -40
  257. dstack/_internal/core/models/pools.py +0 -43
  258. dstack/_internal/server/routers/pools.py +0 -142
  259. dstack/_internal/server/schemas/pools.py +0 -38
  260. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  261. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  262. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  263. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  264. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  265. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  266. dstack/_internal/server/services/backends/configurators/runpod.py +0 -97
  267. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  268. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  269. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  270. dstack/api/_public/pools.py +0 -41
  271. dstack/api/_public/resources.py +0 -105
  272. dstack/api/server/_pools.py +0 -63
  273. tests/_internal/server/routers/test_pools.py +0 -612
  274. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  275. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/LICENSE.md +0 -0
  276. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/WHEEL +0 -0
  277. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/entry_points.txt +0 -0
  278. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
13
13
 
14
14
  from dstack._internal.core.models.backends.base import BackendType
15
15
  from dstack._internal.core.models.common import ApplyAction
16
- from dstack._internal.core.models.configurations import ServiceConfiguration
16
+ from dstack._internal.core.models.configurations import (
17
+ AnyRunConfiguration,
18
+ DevEnvironmentConfiguration,
19
+ ScalingSpec,
20
+ ServiceConfiguration,
21
+ TaskConfiguration,
22
+ )
17
23
  from dstack._internal.core.models.gateways import GatewayStatus
18
24
  from dstack._internal.core.models.instances import (
19
25
  InstanceAvailability,
@@ -22,15 +28,12 @@ from dstack._internal.core.models.instances import (
22
28
  InstanceType,
23
29
  Resources,
24
30
  )
25
- from dstack._internal.core.models.profiles import DEFAULT_POOL_NAME, Profile
26
- from dstack._internal.core.models.resources import Range, ResourcesSpec
31
+ from dstack._internal.core.models.resources import Range
27
32
  from dstack._internal.core.models.runs import (
28
33
  ApplyRunPlanInput,
29
- JobProvisioningData,
30
34
  JobSpec,
31
35
  JobStatus,
32
36
  JobTerminationReason,
33
- Requirements,
34
37
  Run,
35
38
  RunSpec,
36
39
  RunStatus,
@@ -38,10 +41,9 @@ from dstack._internal.core.models.runs import (
38
41
  )
39
42
  from dstack._internal.core.models.users import GlobalRole, ProjectRole
40
43
  from dstack._internal.core.models.volumes import InstanceMountPoint, MountPoint
41
- from dstack._internal.server.background.tasks.process_instances import process_instances
42
44
  from dstack._internal.server.main import app
43
45
  from dstack._internal.server.models import JobModel, RunModel
44
- from dstack._internal.server.schemas.runs import ApplyRunPlanRequest, CreateInstanceRequest
46
+ from dstack._internal.server.schemas.runs import ApplyRunPlanRequest
45
47
  from dstack._internal.server.services.projects import add_project_member
46
48
  from dstack._internal.server.services.runs import run_model_to_run
47
49
  from dstack._internal.server.testing.common import (
@@ -50,7 +52,6 @@ from dstack._internal.server.testing.common import (
50
52
  create_gateway_compute,
51
53
  create_instance,
52
54
  create_job,
53
- create_pool,
54
55
  create_project,
55
56
  create_repo,
56
57
  create_run,
@@ -114,18 +115,14 @@ def get_dev_env_run_plan_dict(
114
115
  "availability_zones": None,
115
116
  "instance_types": None,
116
117
  "creation_policy": None,
117
- "instance_name": None,
118
118
  "single_branch": None,
119
119
  "max_duration": "off",
120
120
  "stop_duration": None,
121
121
  "max_price": None,
122
- "pool_name": DEFAULT_POOL_NAME,
123
122
  "retry": None,
124
- "retry_policy": None,
125
123
  "spot_policy": "spot",
126
124
  "idle_duration": None,
127
- "termination_idle_time": 300,
128
- "termination_policy": None,
125
+ "utilization_policy": None,
129
126
  "reservation": None,
130
127
  },
131
128
  "configuration_path": "dstack.yaml",
@@ -136,18 +133,14 @@ def get_dev_env_run_plan_dict(
136
133
  "instance_types": None,
137
134
  "creation_policy": None,
138
135
  "default": False,
139
- "instance_name": None,
140
136
  "max_duration": "off",
141
137
  "stop_duration": None,
142
138
  "max_price": None,
143
139
  "name": "string",
144
- "pool_name": DEFAULT_POOL_NAME,
145
140
  "retry": None,
146
- "retry_policy": None,
147
141
  "spot_policy": "spot",
148
142
  "idle_duration": None,
149
- "termination_idle_time": 300,
150
- "termination_policy": None,
143
+ "utilization_policy": None,
151
144
  "reservation": None,
152
145
  },
153
146
  "repo_code_hash": None,
@@ -190,6 +183,7 @@ def get_dev_env_run_plan_dict(
190
183
  "single_branch": False,
191
184
  "max_duration": None,
192
185
  "stop_duration": 300,
186
+ "utilization_policy": None,
193
187
  "registry_auth": None,
194
188
  "requirements": {
195
189
  "resources": {
@@ -205,7 +199,7 @@ def get_dev_env_run_plan_dict(
205
199
  },
206
200
  "retry": None,
207
201
  "volumes": volumes,
208
- "retry_policy": {"retry": False, "duration": None},
202
+ "ssh_key": None,
209
203
  "working_dir": ".",
210
204
  },
211
205
  "offers": [json.loads(o.json()) for o in offers],
@@ -271,18 +265,14 @@ def get_dev_env_run_dict(
271
265
  "availability_zones": None,
272
266
  "instance_types": None,
273
267
  "creation_policy": None,
274
- "instance_name": None,
275
268
  "single_branch": None,
276
269
  "max_duration": "off",
277
270
  "stop_duration": None,
278
271
  "max_price": None,
279
- "pool_name": DEFAULT_POOL_NAME,
280
272
  "retry": None,
281
- "retry_policy": None,
282
273
  "spot_policy": "spot",
283
274
  "idle_duration": None,
284
- "termination_idle_time": 300,
285
- "termination_policy": None,
275
+ "utilization_policy": None,
286
276
  "reservation": None,
287
277
  },
288
278
  "configuration_path": "dstack.yaml",
@@ -293,18 +283,14 @@ def get_dev_env_run_dict(
293
283
  "instance_types": None,
294
284
  "creation_policy": None,
295
285
  "default": False,
296
- "instance_name": None,
297
286
  "max_duration": "off",
298
287
  "stop_duration": None,
299
288
  "max_price": None,
300
289
  "name": "string",
301
- "pool_name": DEFAULT_POOL_NAME,
302
290
  "retry": None,
303
- "retry_policy": None,
304
291
  "spot_policy": "spot",
305
292
  "idle_duration": None,
306
- "termination_idle_time": 300,
307
- "termination_policy": None,
293
+ "utilization_policy": None,
308
294
  "reservation": None,
309
295
  },
310
296
  "repo_code_hash": None,
@@ -347,6 +333,7 @@ def get_dev_env_run_dict(
347
333
  "single_branch": False,
348
334
  "max_duration": None,
349
335
  "stop_duration": 300,
336
+ "utilization_policy": None,
350
337
  "registry_auth": None,
351
338
  "requirements": {
352
339
  "resources": {
@@ -362,7 +349,7 @@ def get_dev_env_run_dict(
362
349
  },
363
350
  "retry": None,
364
351
  "volumes": [],
365
- "retry_policy": {"retry": False, "duration": None},
352
+ "ssh_key": None,
366
353
  "working_dir": ".",
367
354
  },
368
355
  "job_submissions": [
@@ -891,66 +878,77 @@ class TestGetRunPlan:
891
878
 
892
879
  @pytest.mark.asyncio
893
880
  @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
894
- async def test_returns_update_action_when_changing_updatable_fields(
895
- self, test_db, session: AsyncSession, client: AsyncClient
896
- ):
897
- user = await create_user(session=session, global_role=GlobalRole.USER)
898
- project = await create_project(session=session, owner=user)
899
- await add_project_member(
900
- session=session, project=project, user=user, project_role=ProjectRole.USER
901
- )
902
- repo = await create_repo(session=session, project_id=project.id)
903
- run_spec = get_run_spec(
904
- run_name="test-service",
905
- repo_id=repo.name,
906
- configuration=ServiceConfiguration(
907
- type="service",
908
- commands=["one", "two"],
909
- port=80,
910
- replicas=1,
881
+ @pytest.mark.parametrize(
882
+ ("old_conf", "new_conf", "action"),
883
+ [
884
+ pytest.param(
885
+ ServiceConfiguration(
886
+ commands=["one", "two"],
887
+ port=80,
888
+ replicas=1,
889
+ scaling=None,
890
+ ),
891
+ ServiceConfiguration(
892
+ commands=["one", "two"],
893
+ port=80,
894
+ replicas="2..4",
895
+ scaling=ScalingSpec(metric="rps", target=5),
896
+ ),
897
+ "update",
898
+ id="update-service",
911
899
  ),
912
- )
913
- run_model = await create_run(
914
- session=session,
915
- project=project,
916
- repo=repo,
917
- user=user,
918
- run_name=run_spec.run_name,
919
- run_spec=run_spec,
920
- )
921
- run = run_model_to_run(run_model)
922
- run_spec.configuration.replicas = 2
923
- response = await client.post(
924
- f"/api/project/{project.name}/runs/get_plan",
925
- headers=get_auth_headers(user.token),
926
- json={"run_spec": run_spec.dict()},
927
- )
928
- assert response.status_code == 200
929
- response_json = response.json()
930
- assert response_json["action"] == "update"
931
- assert response_json["current_resource"] == json.loads(run.json())
932
-
933
- @pytest.mark.asyncio
934
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
935
- async def test_returns_create_action_when_changing_non_updatable_fields(
936
- self, test_db, session: AsyncSession, client: AsyncClient
937
- ):
900
+ pytest.param(
901
+ ServiceConfiguration(
902
+ commands=["one", "two"],
903
+ port=80,
904
+ replicas=1,
905
+ scaling=None,
906
+ ),
907
+ ServiceConfiguration(
908
+ commands=["one", "two"],
909
+ port=8080, # not updatable
910
+ replicas="2..4",
911
+ scaling=ScalingSpec(metric="rps", target=5),
912
+ ),
913
+ "create",
914
+ id="no-update-service",
915
+ ),
916
+ pytest.param(
917
+ DevEnvironmentConfiguration(ide="vscode", inactivity_duration=False),
918
+ DevEnvironmentConfiguration(ide="vscode", inactivity_duration="30m"),
919
+ "update",
920
+ id="update-dev-env",
921
+ ),
922
+ pytest.param(
923
+ TaskConfiguration(image="test-image-1"),
924
+ TaskConfiguration(image="test-image-2"),
925
+ "create",
926
+ id="no-update-task",
927
+ ),
928
+ pytest.param(
929
+ DevEnvironmentConfiguration(ide="vscode", image="test-image"),
930
+ TaskConfiguration(image="test-image"),
931
+ "create",
932
+ id="no-update-on-type-change",
933
+ ),
934
+ ],
935
+ )
936
+ async def test_returns_update_or_create_action_on_conf_change(
937
+ self,
938
+ test_db,
939
+ session: AsyncSession,
940
+ client: AsyncClient,
941
+ old_conf: AnyRunConfiguration,
942
+ new_conf: AnyRunConfiguration,
943
+ action: str,
944
+ ) -> None:
938
945
  user = await create_user(session=session, global_role=GlobalRole.USER)
939
946
  project = await create_project(session=session, owner=user)
940
947
  await add_project_member(
941
948
  session=session, project=project, user=user, project_role=ProjectRole.USER
942
949
  )
943
950
  repo = await create_repo(session=session, project_id=project.id)
944
- run_spec = get_run_spec(
945
- run_name="test-service",
946
- repo_id=repo.name,
947
- configuration=ServiceConfiguration(
948
- type="service",
949
- commands=["one", "two"],
950
- port=80,
951
- replicas=1,
952
- ),
953
- )
951
+ run_spec = get_run_spec(run_name="test-run", repo_id=repo.name, configuration=old_conf)
954
952
  run_model = await create_run(
955
953
  session=session,
956
954
  project=project,
@@ -960,7 +958,7 @@ class TestGetRunPlan:
960
958
  run_spec=run_spec,
961
959
  )
962
960
  run = run_model_to_run(run_model)
963
- run_spec.configuration.port = 8080
961
+ run_spec.configuration = new_conf
964
962
  response = await client.post(
965
963
  f"/api/project/{project.name}/runs/get_plan",
966
964
  headers=get_auth_headers(user.token),
@@ -968,7 +966,7 @@ class TestGetRunPlan:
968
966
  )
969
967
  assert response.status_code == 200
970
968
  response_json = response.json()
971
- assert response_json["action"] == "create"
969
+ assert response_json["action"] == action
972
970
  assert response_json["current_resource"] == json.loads(run.json())
973
971
 
974
972
 
@@ -1318,11 +1316,9 @@ class TestStopRuns:
1318
1316
  user=user,
1319
1317
  status=RunStatus.RUNNING,
1320
1318
  )
1321
- pool = await create_pool(session=session, project=project)
1322
1319
  instance = await create_instance(
1323
1320
  session=session,
1324
1321
  project=project,
1325
- pool=pool,
1326
1322
  status=InstanceStatus.BUSY,
1327
1323
  )
1328
1324
  job = await create_job(
@@ -1471,194 +1467,6 @@ class TestDeleteRuns:
1471
1467
  assert len(res.scalars().all()) == 1
1472
1468
 
1473
1469
 
1474
- class TestCreateInstance:
1475
- @pytest.mark.asyncio
1476
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
1477
- async def test_returns_403_if_not_project_member(
1478
- self, test_db, session: AsyncSession, client: AsyncClient
1479
- ):
1480
- user = await create_user(session=session, global_role=GlobalRole.USER)
1481
- project = await create_project(session=session, owner=user)
1482
- response = await client.post(
1483
- f"/api/project/{project.name}/runs/create_instance",
1484
- headers=get_auth_headers(user.token),
1485
- )
1486
- assert response.status_code == 403
1487
-
1488
- @pytest.mark.asyncio
1489
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
1490
- async def test_creates_instance(self, test_db, session: AsyncSession, client: AsyncClient):
1491
- user = await create_user(session=session, global_role=GlobalRole.USER)
1492
- project = await create_project(session=session, owner=user)
1493
- await add_project_member(
1494
- session=session, project=project, user=user, project_role=ProjectRole.USER
1495
- )
1496
- request = CreateInstanceRequest(
1497
- profile=Profile(name="test_profile"),
1498
- requirements=Requirements(resources=ResourcesSpec(cpu=1)),
1499
- )
1500
- instance_id = UUID("1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e")
1501
- with (
1502
- patch(
1503
- "dstack._internal.server.services.offers.get_offers_by_requirements"
1504
- ) as run_plan_by_req,
1505
- patch("uuid.uuid4") as uuid_mock,
1506
- ):
1507
- uuid_mock.return_value = instance_id
1508
- offer = InstanceOfferWithAvailability(
1509
- backend=BackendType.AWS,
1510
- instance=InstanceType(
1511
- name="instance",
1512
- resources=Resources(cpus=1, memory_mib=512, spot=False, gpus=[]),
1513
- ),
1514
- region="eu",
1515
- price=1.0,
1516
- availability=InstanceAvailability.AVAILABLE,
1517
- )
1518
- backend = Mock()
1519
- backend.compute.return_value.get_offers_cached.return_value = [offer]
1520
- backend.compute.return_value.create_instance.return_value = JobProvisioningData(
1521
- backend=offer.backend,
1522
- instance_type=offer.instance,
1523
- instance_id="test_instance",
1524
- hostname="1.1.1.1",
1525
- internal_ip=None,
1526
- region=offer.region,
1527
- price=offer.price,
1528
- username="ubuntu",
1529
- ssh_port=22,
1530
- ssh_proxy=None,
1531
- dockerized=True,
1532
- backend_data=None,
1533
- )
1534
- backend.TYPE = BackendType.AWS
1535
- run_plan_by_req.return_value = [(backend, offer)]
1536
- response = await client.post(
1537
- f"/api/project/{project.name}/runs/create_instance",
1538
- headers=get_auth_headers(user.token),
1539
- json=request.dict(),
1540
- )
1541
- assert response.status_code == 200
1542
- result = response.json()
1543
- expected = {
1544
- "id": str(instance_id),
1545
- "project_name": project.name,
1546
- "backend": None,
1547
- "instance_type": None,
1548
- "name": result["name"],
1549
- "fleet_id": None,
1550
- "fleet_name": None,
1551
- "instance_num": 0,
1552
- "job_name": None,
1553
- "hostname": None,
1554
- "status": "pending",
1555
- "unreachable": False,
1556
- "termination_reason": None,
1557
- "created": result["created"],
1558
- "pool_name": None,
1559
- "region": None,
1560
- "availability_zone": None,
1561
- "price": None,
1562
- "total_blocks": 1,
1563
- "busy_blocks": 0,
1564
- }
1565
- assert result == expected
1566
-
1567
- @pytest.mark.asyncio
1568
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
1569
- async def test_error_if_backends_do_not_support_create_instance(
1570
- self, test_db, session: AsyncSession, client: AsyncClient
1571
- ):
1572
- user = await create_user(session=session, global_role=GlobalRole.USER)
1573
- project = await create_project(session=session, owner=user)
1574
- await add_project_member(
1575
- session=session, project=project, user=user, project_role=ProjectRole.USER
1576
- )
1577
- request = CreateInstanceRequest(
1578
- profile=Profile(name="test_profile"),
1579
- requirements=Requirements(resources=ResourcesSpec(cpu=1)),
1580
- )
1581
- with patch(
1582
- "dstack._internal.server.services.offers.get_offers_by_requirements"
1583
- ) as run_plan_by_req:
1584
- offer = InstanceOfferWithAvailability(
1585
- backend=BackendType.AZURE,
1586
- instance=InstanceType(
1587
- name="instance",
1588
- resources=Resources(cpus=1, memory_mib=512, spot=False, gpus=[]),
1589
- ),
1590
- region="eu",
1591
- price=1.0,
1592
- availability=InstanceAvailability.AVAILABLE,
1593
- )
1594
- backend = Mock()
1595
- backend.TYPE = BackendType.AZURE
1596
- backend.compute.return_value.get_offers_cached.return_value = [offer]
1597
- backend.compute.return_value.create_instance.side_effect = NotImplementedError()
1598
- run_plan_by_req.return_value = [(backend, offer)]
1599
- response = await client.post(
1600
- f"/api/project/{project.name}/runs/create_instance",
1601
- headers=get_auth_headers(user.token),
1602
- json=request.dict(),
1603
- )
1604
- assert response.status_code == 200
1605
- await process_instances()
1606
-
1607
- @pytest.mark.asyncio
1608
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
1609
- async def test_backend_does_not_support_create_instance(
1610
- self, test_db, session: AsyncSession, client: AsyncClient
1611
- ):
1612
- user = await create_user(session=session, global_role=GlobalRole.USER)
1613
- project = await create_project(session=session, owner=user)
1614
- await add_project_member(
1615
- session=session, project=project, user=user, project_role=ProjectRole.USER
1616
- )
1617
- request = CreateInstanceRequest(
1618
- profile=Profile(name="test_profile"),
1619
- requirements=Requirements(resources=ResourcesSpec(cpu=1)),
1620
- )
1621
-
1622
- with patch(
1623
- "dstack._internal.server.services.offers.get_offers_by_requirements"
1624
- ) as run_plan_by_req:
1625
- offers = InstanceOfferWithAvailability(
1626
- backend=BackendType.VASTAI,
1627
- instance=InstanceType(
1628
- name="instance",
1629
- resources=Resources(cpus=1, memory_mib=512, spot=False, gpus=[]),
1630
- ),
1631
- region="eu",
1632
- price=1.0,
1633
- availability=InstanceAvailability.AVAILABLE,
1634
- )
1635
-
1636
- backend = Mock()
1637
- backend.TYPE = BackendType.VASTAI
1638
- backend.compute.return_value.get_offers_cached.return_value = [offers]
1639
- backend.compute.return_value.create_instance.side_effect = NotImplementedError()
1640
- run_plan_by_req.return_value = [(backend, offers)]
1641
-
1642
- response = await client.post(
1643
- f"/api/project/{project.name}/runs/create_instance",
1644
- headers=get_auth_headers(user.token),
1645
- json=request.dict(),
1646
- )
1647
-
1648
- assert response.status_code == 400
1649
-
1650
- result = response.json()
1651
- expected = {
1652
- "detail": [
1653
- {
1654
- "msg": "Backends do not support create_instance. Try to select other backends.",
1655
- "code": "error",
1656
- }
1657
- ]
1658
- }
1659
- assert result == expected
1660
-
1661
-
1662
1470
  @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
1663
1471
  class TestSubmitService:
1664
1472
  @pytest.fixture(autouse=True)
@@ -14,8 +14,8 @@ from dstack._internal.core.models.users import GlobalRole, ProjectRole
14
14
  from dstack._internal.server.models import VolumeAttachmentModel, VolumeModel
15
15
  from dstack._internal.server.services.projects import add_project_member
16
16
  from dstack._internal.server.testing.common import (
17
+ ComputeMockSpec,
17
18
  create_instance,
18
- create_pool,
19
19
  create_project,
20
20
  create_user,
21
21
  create_volume,
@@ -366,6 +366,7 @@ class TestDeleteVolumes:
366
366
  ) as m:
367
367
  aws_mock = Mock()
368
368
  m.return_value = aws_mock
369
+ aws_mock.compute.return_value = Mock(spec=ComputeMockSpec)
369
370
  response = await client.post(
370
371
  f"/api/project/{project.name}/volumes/delete",
371
372
  headers=get_auth_headers(user.token),
@@ -383,7 +384,6 @@ class TestDeleteVolumes:
383
384
  ):
384
385
  user = await create_user(session, global_role=GlobalRole.USER)
385
386
  project = await create_project(session)
386
- pool = await create_pool(session=session, project=project)
387
387
  await add_project_member(
388
388
  session=session, project=project, user=user, project_role=ProjectRole.USER
389
389
  )
@@ -396,7 +396,6 @@ class TestDeleteVolumes:
396
396
  instance = await create_instance(
397
397
  session=session,
398
398
  project=project,
399
- pool=pool,
400
399
  )
401
400
  volume.attachments.append(VolumeAttachmentModel(instance=instance))
402
401
  await session.commit()
File without changes
@@ -0,0 +1,35 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.models.configurations import TaskConfiguration
6
+ from dstack._internal.core.models.runs import JobSSHKey
7
+ from dstack._internal.server.services.jobs.configurators.task import TaskJobConfigurator
8
+ from dstack._internal.server.testing.common import get_run_spec
9
+
10
+
11
+ @pytest.mark.asyncio
12
+ @pytest.mark.usefixtures("image_config_mock")
13
+ class TestTaskJobConfigurator:
14
+ async def test_ssh_key_single_node(self):
15
+ configuration = TaskConfiguration(nodes=1, image="debian")
16
+ run_spec = get_run_spec(run_name="run", repo_id="id", configuration=configuration)
17
+ configurator = TaskJobConfigurator(run_spec)
18
+
19
+ job_specs = await configurator.get_job_specs(replica_num=0)
20
+
21
+ assert len(job_specs) == 1
22
+ assert job_specs[0].ssh_key is None
23
+
24
+ async def test_ssh_key_multi_node(self):
25
+ configuration = TaskConfiguration(nodes=2, image="debian")
26
+ run_spec = get_run_spec(run_name="run", repo_id="id", configuration=configuration)
27
+ configurator = TaskJobConfigurator(run_spec)
28
+
29
+ with patch("dstack._internal.utils.crypto.generate_rsa_key_pair_bytes") as gen_mock:
30
+ gen_mock.side_effect = [(b"private1", b"public1"), (b"private2", b"public2")]
31
+ job_specs = await configurator.get_job_specs(replica_num=0)
32
+
33
+ assert len(job_specs) == 2
34
+ assert job_specs[0].ssh_key == JobSSHKey(private="private1", public="public1")
35
+ assert job_specs[1].ssh_key == JobSSHKey(private="private1", public="public1")
@@ -6,11 +6,14 @@ import yaml
6
6
  from sqlalchemy import select
7
7
  from sqlalchemy.ext.asyncio import AsyncSession
8
8
 
9
- from dstack._internal.core.models.backends.azure import AzureConfigInfoWithCreds, AzureDefaultCreds
9
+ from dstack._internal.core.backends.azure.models import (
10
+ AzureBackendConfigWithCreds,
11
+ AzureDefaultCreds,
12
+ )
10
13
  from dstack._internal.core.models.backends.base import BackendType
11
14
  from dstack._internal.server import settings
12
15
  from dstack._internal.server.models import BackendModel, ProjectModel
13
- from dstack._internal.server.services.config import AzureConfig, ServerConfigManager
16
+ from dstack._internal.server.services.config import ServerConfigManager
14
17
  from dstack._internal.server.testing.common import (
15
18
  create_project,
16
19
  create_user,
@@ -38,7 +41,7 @@ class TestServerConfigManager:
38
41
  ) as create_backend_mock,
39
42
  ):
40
43
  list_available_backend_types_mock.return_value = [BackendType.AZURE]
41
- default_config = AzureConfigInfoWithCreds(
44
+ default_config = AzureBackendConfigWithCreds(
42
45
  tenant_id="test_tenant",
43
46
  subscription_id="test_subscription",
44
47
  locations=["westeurope"],
@@ -52,7 +55,7 @@ class TestServerConfigManager:
52
55
  list_available_backend_types_mock.assert_called()
53
56
  get_configurator_mock.assert_called()
54
57
  create_backend_mock.assert_called()
55
- assert manager.config.projects[0].backends[0] == AzureConfig(
58
+ assert manager.config.projects[0].backends[0] == AzureBackendConfigWithCreds(
56
59
  tenant_id="test_tenant",
57
60
  subscription_id="test_subscription",
58
61
  regions=["westeurope"],
@@ -4,7 +4,7 @@ from unittest.mock import Mock
4
4
  import pytest
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
6
 
7
- from dstack._internal.core.backends.base import Backend
7
+ from dstack._internal.core.backends.base.backend import Backend
8
8
  from dstack._internal.core.errors import ServerClientError
9
9
  from dstack._internal.core.models.backends.base import BackendType
10
10
  from dstack._internal.core.models.fleets import (
@@ -20,7 +20,6 @@ from dstack._internal.server.services.fleets import get_plan
20
20
  from dstack._internal.server.testing.common import (
21
21
  create_fleet,
22
22
  create_instance,
23
- create_pool,
24
23
  create_project,
25
24
  create_user,
26
25
  get_fleet_spec,
@@ -45,7 +44,6 @@ class TestGetPlanSSHFleetHostsValidation:
45
44
  self, session: AsyncSession, project: ProjectModel, spec: FleetSpec
46
45
  ) -> FleetModel:
47
46
  assert spec.configuration.ssh_config is not None, spec.configuration
48
- pool = await create_pool(session=session, project=project)
49
47
  fleet = await create_fleet(session=session, project=project, spec=spec)
50
48
  for host in spec.configuration.ssh_config.hosts:
51
49
  if isinstance(host, SSHHostParams):
@@ -56,7 +54,6 @@ class TestGetPlanSSHFleetHostsValidation:
56
54
  await create_instance(
57
55
  session=session,
58
56
  project=project,
59
- pool=pool,
60
57
  fleet=fleet,
61
58
  backend=BackendType.REMOTE,
62
59
  remote_connection_info=rci,