dstack 0.0.9__py3-none-any.whl → 0.20.7__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 (879) hide show
  1. dstack/_internal/cli/commands/__init__.py +80 -0
  2. dstack/_internal/cli/commands/apply.py +100 -0
  3. dstack/_internal/cli/commands/attach.py +161 -0
  4. dstack/_internal/cli/commands/completion.py +22 -0
  5. dstack/_internal/cli/commands/delete.py +44 -0
  6. dstack/_internal/cli/commands/event.py +168 -0
  7. dstack/_internal/cli/commands/fleet.py +161 -0
  8. dstack/_internal/cli/commands/gateway.py +159 -0
  9. dstack/_internal/cli/commands/init.py +64 -0
  10. dstack/_internal/cli/commands/login.py +352 -0
  11. dstack/_internal/cli/commands/logs.py +62 -0
  12. dstack/_internal/cli/commands/metrics.py +153 -0
  13. dstack/_internal/cli/commands/offer.py +146 -0
  14. dstack/_internal/cli/commands/project.py +259 -0
  15. dstack/_internal/cli/commands/ps.py +81 -0
  16. dstack/_internal/cli/commands/run.py +69 -0
  17. dstack/_internal/cli/commands/secrets.py +92 -0
  18. dstack/_internal/cli/commands/server.py +96 -0
  19. dstack/_internal/cli/commands/stop.py +26 -0
  20. dstack/_internal/cli/commands/volume.py +117 -0
  21. dstack/_internal/cli/main.py +101 -0
  22. dstack/_internal/cli/models/gateways.py +16 -0
  23. dstack/_internal/cli/models/offers.py +47 -0
  24. dstack/_internal/cli/models/runs.py +16 -0
  25. dstack/_internal/cli/services/args.py +31 -0
  26. dstack/_internal/cli/services/completion.py +91 -0
  27. dstack/_internal/cli/services/configurators/__init__.py +86 -0
  28. dstack/_internal/cli/services/configurators/base.py +103 -0
  29. dstack/_internal/cli/services/configurators/fleet.py +475 -0
  30. dstack/_internal/cli/services/configurators/gateway.py +231 -0
  31. dstack/_internal/cli/services/configurators/run.py +882 -0
  32. dstack/_internal/cli/services/configurators/volume.py +222 -0
  33. dstack/_internal/cli/services/events.py +68 -0
  34. dstack/_internal/cli/services/profile.py +182 -0
  35. dstack/_internal/cli/services/repos.py +71 -0
  36. dstack/_internal/cli/services/resources.py +54 -0
  37. dstack/_internal/cli/utils/common.py +159 -0
  38. dstack/_internal/cli/utils/fleet.py +106 -0
  39. dstack/_internal/cli/utils/gateway.py +56 -0
  40. dstack/_internal/cli/utils/gpu.py +178 -0
  41. dstack/_internal/cli/utils/rich.py +156 -0
  42. dstack/_internal/cli/utils/run.py +517 -0
  43. dstack/_internal/cli/utils/secrets.py +25 -0
  44. dstack/_internal/cli/utils/updates.py +98 -0
  45. dstack/_internal/cli/utils/volume.py +58 -0
  46. dstack/_internal/compat.py +3 -0
  47. dstack/_internal/core/backends/amddevcloud/__init__.py +1 -0
  48. dstack/_internal/core/backends/amddevcloud/backend.py +16 -0
  49. dstack/_internal/core/backends/amddevcloud/compute.py +5 -0
  50. dstack/_internal/core/backends/amddevcloud/configurator.py +29 -0
  51. dstack/_internal/core/backends/aws/auth.py +30 -0
  52. dstack/_internal/core/backends/aws/backend.py +31 -0
  53. dstack/_internal/core/backends/aws/compute.py +1153 -0
  54. dstack/_internal/core/backends/aws/configurator.py +191 -0
  55. dstack/_internal/core/backends/aws/models.py +135 -0
  56. dstack/_internal/core/backends/aws/resources.py +700 -0
  57. dstack/_internal/core/backends/azure/auth.py +39 -0
  58. dstack/_internal/core/backends/azure/backend.py +21 -0
  59. dstack/_internal/core/backends/azure/compute.py +676 -0
  60. dstack/_internal/core/backends/azure/configurator.py +472 -0
  61. dstack/_internal/core/backends/azure/models.py +98 -0
  62. dstack/_internal/core/backends/azure/resources.py +116 -0
  63. dstack/_internal/core/backends/azure/utils.py +42 -0
  64. dstack/_internal/core/backends/base/backend.py +18 -0
  65. dstack/_internal/core/backends/base/compute.py +1101 -0
  66. dstack/_internal/core/backends/base/configurator.py +117 -0
  67. dstack/_internal/core/backends/base/models.py +24 -0
  68. dstack/_internal/core/backends/base/offers.py +232 -0
  69. dstack/_internal/core/backends/cloudrift/api_client.py +220 -0
  70. dstack/_internal/core/backends/cloudrift/backend.py +16 -0
  71. dstack/_internal/core/backends/cloudrift/compute.py +138 -0
  72. dstack/_internal/core/backends/cloudrift/configurator.py +72 -0
  73. dstack/_internal/core/backends/cloudrift/models.py +40 -0
  74. dstack/_internal/core/backends/configurators.py +181 -0
  75. dstack/_internal/core/backends/cudo/__init__.py +0 -0
  76. dstack/_internal/core/backends/cudo/api_client.py +111 -0
  77. dstack/_internal/core/backends/cudo/backend.py +16 -0
  78. dstack/_internal/core/backends/cudo/compute.py +174 -0
  79. dstack/_internal/core/backends/cudo/configurator.py +63 -0
  80. dstack/_internal/core/backends/cudo/models.py +37 -0
  81. dstack/_internal/core/backends/datacrunch/__init__.py +1 -0
  82. dstack/_internal/core/backends/datacrunch/backend.py +18 -0
  83. dstack/_internal/core/backends/datacrunch/compute.py +8 -0
  84. dstack/_internal/core/backends/datacrunch/configurator.py +17 -0
  85. dstack/_internal/core/backends/digitalocean/__init__.py +1 -0
  86. dstack/_internal/core/backends/digitalocean/backend.py +16 -0
  87. dstack/_internal/core/backends/digitalocean/compute.py +5 -0
  88. dstack/_internal/core/backends/digitalocean/configurator.py +31 -0
  89. dstack/_internal/core/backends/digitalocean_base/__init__.py +1 -0
  90. dstack/_internal/core/backends/digitalocean_base/api_client.py +104 -0
  91. dstack/_internal/core/backends/digitalocean_base/backend.py +5 -0
  92. dstack/_internal/core/backends/digitalocean_base/compute.py +174 -0
  93. dstack/_internal/core/backends/digitalocean_base/configurator.py +57 -0
  94. dstack/_internal/core/backends/digitalocean_base/models.py +43 -0
  95. dstack/_internal/core/backends/dstack/__init__.py +0 -0
  96. dstack/_internal/core/backends/dstack/models.py +26 -0
  97. dstack/_internal/core/backends/features.py +74 -0
  98. dstack/_internal/core/backends/gcp/__init__.py +0 -0
  99. dstack/_internal/core/backends/gcp/auth.py +57 -0
  100. dstack/_internal/core/backends/gcp/backend.py +17 -0
  101. dstack/_internal/core/backends/gcp/compute.py +1257 -0
  102. dstack/_internal/core/backends/gcp/configurator.py +206 -0
  103. dstack/_internal/core/backends/gcp/features/__init__.py +0 -0
  104. dstack/_internal/core/backends/gcp/features/tcpx.py +65 -0
  105. dstack/_internal/core/backends/gcp/models.py +160 -0
  106. dstack/_internal/core/backends/gcp/resources.py +585 -0
  107. dstack/_internal/core/backends/hotaisle/__init__.py +1 -0
  108. dstack/_internal/core/backends/hotaisle/api_client.py +101 -0
  109. dstack/_internal/core/backends/hotaisle/backend.py +16 -0
  110. dstack/_internal/core/backends/hotaisle/compute.py +188 -0
  111. dstack/_internal/core/backends/hotaisle/configurator.py +66 -0
  112. dstack/_internal/core/backends/hotaisle/models.py +45 -0
  113. dstack/_internal/core/backends/kubernetes/__init__.py +0 -0
  114. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  115. dstack/_internal/core/backends/kubernetes/compute.py +1077 -0
  116. dstack/_internal/core/backends/kubernetes/configurator.py +61 -0
  117. dstack/_internal/core/backends/kubernetes/models.py +71 -0
  118. dstack/_internal/core/backends/kubernetes/utils.py +81 -0
  119. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -0
  120. dstack/_internal/core/backends/lambdalabs/api_client.py +87 -0
  121. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  122. dstack/_internal/core/backends/lambdalabs/compute.py +233 -0
  123. dstack/_internal/core/backends/lambdalabs/configurator.py +65 -0
  124. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  125. dstack/_internal/core/backends/local/__init__.py +0 -0
  126. dstack/_internal/core/backends/local/backend.py +14 -0
  127. dstack/_internal/core/backends/local/compute.py +130 -0
  128. dstack/_internal/core/backends/models.py +158 -0
  129. dstack/_internal/core/backends/nebius/__init__.py +0 -0
  130. dstack/_internal/core/backends/nebius/backend.py +16 -0
  131. dstack/_internal/core/backends/nebius/compute.py +401 -0
  132. dstack/_internal/core/backends/nebius/configurator.py +98 -0
  133. dstack/_internal/core/backends/nebius/models.py +185 -0
  134. dstack/_internal/core/backends/nebius/resources.py +433 -0
  135. dstack/_internal/core/backends/oci/__init__.py +0 -0
  136. dstack/_internal/core/backends/oci/auth.py +21 -0
  137. dstack/_internal/core/backends/oci/backend.py +16 -0
  138. dstack/_internal/core/backends/oci/compute.py +209 -0
  139. dstack/_internal/core/backends/oci/configurator.py +156 -0
  140. dstack/_internal/core/backends/oci/exceptions.py +15 -0
  141. dstack/_internal/core/backends/oci/models.py +87 -0
  142. dstack/_internal/core/backends/oci/region.py +86 -0
  143. dstack/_internal/core/backends/oci/resources.py +836 -0
  144. dstack/_internal/core/backends/runpod/__init__.py +0 -0
  145. dstack/_internal/core/backends/runpod/api_client.py +627 -0
  146. dstack/_internal/core/backends/runpod/backend.py +16 -0
  147. dstack/_internal/core/backends/runpod/compute.py +444 -0
  148. dstack/_internal/core/backends/runpod/configurator.py +63 -0
  149. dstack/_internal/core/backends/runpod/models.py +54 -0
  150. dstack/_internal/core/backends/template/__init__.py +0 -0
  151. dstack/_internal/core/backends/template/backend.py.jinja +16 -0
  152. dstack/_internal/core/backends/template/compute.py.jinja +95 -0
  153. dstack/_internal/core/backends/template/configurator.py.jinja +69 -0
  154. dstack/_internal/core/backends/template/models.py.jinja +62 -0
  155. dstack/_internal/core/backends/tensordock/models.py +40 -0
  156. dstack/_internal/core/backends/vastai/__init__.py +0 -0
  157. dstack/_internal/core/backends/vastai/api_client.py +143 -0
  158. dstack/_internal/core/backends/vastai/backend.py +16 -0
  159. dstack/_internal/core/backends/vastai/compute.py +141 -0
  160. dstack/_internal/core/backends/vastai/configurator.py +69 -0
  161. dstack/_internal/core/backends/vastai/models.py +37 -0
  162. dstack/_internal/core/backends/verda/__init__.py +0 -0
  163. dstack/_internal/core/backends/verda/backend.py +16 -0
  164. dstack/_internal/core/backends/verda/compute.py +266 -0
  165. dstack/_internal/core/backends/verda/configurator.py +73 -0
  166. dstack/_internal/core/backends/verda/models.py +38 -0
  167. dstack/_internal/core/backends/vultr/__init__.py +0 -0
  168. dstack/_internal/core/backends/vultr/api_client.py +116 -0
  169. dstack/_internal/core/backends/vultr/backend.py +16 -0
  170. dstack/_internal/core/backends/vultr/compute.py +167 -0
  171. dstack/_internal/core/backends/vultr/configurator.py +71 -0
  172. dstack/_internal/core/backends/vultr/models.py +34 -0
  173. dstack/_internal/core/compatibility/__init__.py +0 -0
  174. dstack/_internal/core/compatibility/events.py +13 -0
  175. dstack/_internal/core/compatibility/fleets.py +58 -0
  176. dstack/_internal/core/compatibility/gateways.py +39 -0
  177. dstack/_internal/core/compatibility/gpus.py +13 -0
  178. dstack/_internal/core/compatibility/logs.py +14 -0
  179. dstack/_internal/core/compatibility/runs.py +86 -0
  180. dstack/_internal/core/compatibility/volumes.py +37 -0
  181. dstack/_internal/core/consts.py +8 -0
  182. dstack/_internal/core/errors.py +160 -0
  183. dstack/_internal/core/models/__init__.py +0 -0
  184. dstack/_internal/core/models/auth.py +28 -0
  185. dstack/_internal/core/models/backends/__init__.py +0 -0
  186. dstack/_internal/core/models/backends/base.py +48 -0
  187. dstack/_internal/core/models/common.py +143 -0
  188. dstack/_internal/core/models/compute_groups.py +39 -0
  189. dstack/_internal/core/models/config.py +28 -0
  190. dstack/_internal/core/models/configurations.py +1123 -0
  191. dstack/_internal/core/models/envs.py +149 -0
  192. dstack/_internal/core/models/events.py +98 -0
  193. dstack/_internal/core/models/files.py +67 -0
  194. dstack/_internal/core/models/fleets.py +437 -0
  195. dstack/_internal/core/models/gateways.py +146 -0
  196. dstack/_internal/core/models/gpus.py +45 -0
  197. dstack/_internal/core/models/health.py +28 -0
  198. dstack/_internal/core/models/instances.py +346 -0
  199. dstack/_internal/core/models/logs.py +27 -0
  200. dstack/_internal/core/models/metrics.py +14 -0
  201. dstack/_internal/core/models/placement.py +27 -0
  202. dstack/_internal/core/models/profiles.py +431 -0
  203. dstack/_internal/core/models/projects.py +46 -0
  204. dstack/_internal/core/models/repos/__init__.py +34 -0
  205. dstack/_internal/core/models/repos/base.py +36 -0
  206. dstack/_internal/core/models/repos/local.py +96 -0
  207. dstack/_internal/core/models/repos/remote.py +341 -0
  208. dstack/_internal/core/models/repos/virtual.py +85 -0
  209. dstack/_internal/core/models/resources.py +424 -0
  210. dstack/_internal/core/models/routers.py +24 -0
  211. dstack/_internal/core/models/runs.py +618 -0
  212. dstack/_internal/core/models/secrets.py +16 -0
  213. dstack/_internal/core/models/server.py +7 -0
  214. dstack/_internal/core/models/services.py +76 -0
  215. dstack/_internal/core/models/unix.py +53 -0
  216. dstack/_internal/core/models/users.py +60 -0
  217. dstack/_internal/core/models/volumes.py +221 -0
  218. dstack/_internal/core/services/__init__.py +16 -0
  219. dstack/_internal/core/services/api_client.py +15 -0
  220. dstack/_internal/core/services/configs/__init__.py +116 -0
  221. dstack/_internal/core/services/diff.py +71 -0
  222. dstack/_internal/core/services/logs.py +58 -0
  223. dstack/_internal/core/services/profiles.py +46 -0
  224. dstack/_internal/core/services/repos.py +236 -0
  225. dstack/_internal/core/services/ssh/__init__.py +27 -0
  226. dstack/_internal/core/services/ssh/attach.py +241 -0
  227. dstack/_internal/core/services/ssh/client.py +113 -0
  228. dstack/_internal/core/services/ssh/key_manager.py +53 -0
  229. dstack/_internal/core/services/ssh/ports.py +89 -0
  230. dstack/_internal/core/services/ssh/tunnel.py +337 -0
  231. dstack/_internal/proxy/__init__.py +8 -0
  232. dstack/_internal/proxy/gateway/__init__.py +0 -0
  233. dstack/_internal/proxy/gateway/app.py +89 -0
  234. dstack/_internal/proxy/gateway/auth.py +26 -0
  235. dstack/_internal/proxy/gateway/const.py +7 -0
  236. dstack/_internal/proxy/gateway/deps.py +73 -0
  237. dstack/_internal/proxy/gateway/main.py +17 -0
  238. dstack/_internal/proxy/gateway/models.py +23 -0
  239. dstack/_internal/proxy/gateway/repo/__init__.py +0 -0
  240. dstack/_internal/proxy/gateway/repo/repo.py +121 -0
  241. dstack/_internal/proxy/gateway/repo/state_v1.py +164 -0
  242. dstack/_internal/proxy/gateway/resources/nginx/00-log-format.conf +11 -0
  243. dstack/_internal/proxy/gateway/resources/nginx/entrypoint.jinja2 +27 -0
  244. dstack/_internal/proxy/gateway/resources/nginx/router_workers.jinja2 +23 -0
  245. dstack/_internal/proxy/gateway/resources/nginx/service.jinja2 +105 -0
  246. dstack/_internal/proxy/gateway/routers/__init__.py +0 -0
  247. dstack/_internal/proxy/gateway/routers/auth.py +10 -0
  248. dstack/_internal/proxy/gateway/routers/config.py +28 -0
  249. dstack/_internal/proxy/gateway/routers/registry.py +124 -0
  250. dstack/_internal/proxy/gateway/routers/stats.py +18 -0
  251. dstack/_internal/proxy/gateway/schemas/__init__.py +0 -0
  252. dstack/_internal/proxy/gateway/schemas/common.py +5 -0
  253. dstack/_internal/proxy/gateway/schemas/config.py +9 -0
  254. dstack/_internal/proxy/gateway/schemas/registry.py +63 -0
  255. dstack/_internal/proxy/gateway/schemas/stats.py +15 -0
  256. dstack/_internal/proxy/gateway/services/__init__.py +0 -0
  257. dstack/_internal/proxy/gateway/services/model_routers/__init__.py +18 -0
  258. dstack/_internal/proxy/gateway/services/model_routers/base.py +91 -0
  259. dstack/_internal/proxy/gateway/services/model_routers/sglang.py +269 -0
  260. dstack/_internal/proxy/gateway/services/nginx.py +455 -0
  261. dstack/_internal/proxy/gateway/services/registry.py +426 -0
  262. dstack/_internal/proxy/gateway/services/server_client.py +95 -0
  263. dstack/_internal/proxy/gateway/services/stats.py +170 -0
  264. dstack/_internal/proxy/gateway/testing/__init__.py +0 -0
  265. dstack/_internal/proxy/gateway/testing/common.py +13 -0
  266. dstack/_internal/proxy/lib/__init__.py +0 -0
  267. dstack/_internal/proxy/lib/auth.py +7 -0
  268. dstack/_internal/proxy/lib/deps.py +106 -0
  269. dstack/_internal/proxy/lib/errors.py +14 -0
  270. dstack/_internal/proxy/lib/models.py +112 -0
  271. dstack/_internal/proxy/lib/repo.py +27 -0
  272. dstack/_internal/proxy/lib/routers/__init__.py +0 -0
  273. dstack/_internal/proxy/lib/routers/model_proxy.py +102 -0
  274. dstack/_internal/proxy/lib/schemas/__init__.py +0 -0
  275. dstack/_internal/proxy/lib/schemas/model_proxy.py +77 -0
  276. dstack/_internal/proxy/lib/services/__init__.py +0 -0
  277. dstack/_internal/proxy/lib/services/model_proxy/__init__.py +0 -0
  278. dstack/_internal/proxy/lib/services/model_proxy/clients/__init__.py +0 -0
  279. dstack/_internal/proxy/lib/services/model_proxy/clients/base.py +18 -0
  280. dstack/_internal/proxy/lib/services/model_proxy/clients/openai.py +67 -0
  281. dstack/_internal/proxy/lib/services/model_proxy/clients/tgi.py +208 -0
  282. dstack/_internal/proxy/lib/services/model_proxy/model_proxy.py +23 -0
  283. dstack/_internal/proxy/lib/services/service_connection.py +160 -0
  284. dstack/_internal/proxy/lib/testing/__init__.py +0 -0
  285. dstack/_internal/proxy/lib/testing/auth.py +11 -0
  286. dstack/_internal/proxy/lib/testing/common.py +51 -0
  287. dstack/_internal/server/__init__.py +0 -0
  288. dstack/_internal/server/alembic.ini +100 -0
  289. dstack/_internal/server/app.py +432 -0
  290. dstack/_internal/server/background/__init__.py +142 -0
  291. dstack/_internal/server/background/tasks/__init__.py +0 -0
  292. dstack/_internal/server/background/tasks/common.py +24 -0
  293. dstack/_internal/server/background/tasks/process_compute_groups.py +167 -0
  294. dstack/_internal/server/background/tasks/process_events.py +17 -0
  295. dstack/_internal/server/background/tasks/process_fleets.py +289 -0
  296. dstack/_internal/server/background/tasks/process_gateways.py +188 -0
  297. dstack/_internal/server/background/tasks/process_idle_volumes.py +145 -0
  298. dstack/_internal/server/background/tasks/process_instances.py +1186 -0
  299. dstack/_internal/server/background/tasks/process_metrics.py +172 -0
  300. dstack/_internal/server/background/tasks/process_placement_groups.py +104 -0
  301. dstack/_internal/server/background/tasks/process_probes.py +164 -0
  302. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +150 -0
  303. dstack/_internal/server/background/tasks/process_running_jobs.py +1238 -0
  304. dstack/_internal/server/background/tasks/process_runs.py +842 -0
  305. dstack/_internal/server/background/tasks/process_submitted_jobs.py +1106 -0
  306. dstack/_internal/server/background/tasks/process_terminating_jobs.py +108 -0
  307. dstack/_internal/server/background/tasks/process_volumes.py +129 -0
  308. dstack/_internal/server/compatibility/__init__.py +0 -0
  309. dstack/_internal/server/compatibility/common.py +20 -0
  310. dstack/_internal/server/compatibility/gpus.py +22 -0
  311. dstack/_internal/server/db.py +127 -0
  312. dstack/_internal/server/deps.py +19 -0
  313. dstack/_internal/server/main.py +4 -0
  314. dstack/_internal/server/migrations/__init__.py +0 -0
  315. dstack/_internal/server/migrations/env.py +112 -0
  316. dstack/_internal/server/migrations/script.py.mako +28 -0
  317. dstack/_internal/server/migrations/versions/006512f572b4_add_projects_original_name.py +38 -0
  318. dstack/_internal/server/migrations/versions/065588ec72b8_add_vultr_to_backendtype_enum.py +81 -0
  319. dstack/_internal/server/migrations/versions/06e977bc61c7_add_usermodel_deleted_and_original_name.py +45 -0
  320. dstack/_internal/server/migrations/versions/0e33559e16ed_update_instancestatus.py +64 -0
  321. dstack/_internal/server/migrations/versions/112753bc17dd_remove_nullable_fields.py +50 -0
  322. dstack/_internal/server/migrations/versions/1338b788b612_reverse_job_instance_relationship.py +71 -0
  323. dstack/_internal/server/migrations/versions/14f2cb002fc2_add_jobmodel_removed_flag.py +44 -0
  324. dstack/_internal/server/migrations/versions/1a48dfe44a40_rework_termination_handling.py +42 -0
  325. dstack/_internal/server/migrations/versions/1aa9638ad963_added_email_index.py +31 -0
  326. dstack/_internal/server/migrations/versions/1e3fb39ef74b_add_remote_connection_details.py +26 -0
  327. dstack/_internal/server/migrations/versions/1e76fb0dde87_add_jobmodel_inactivity_secs.py +32 -0
  328. dstack/_internal/server/migrations/versions/20166748b60c_add_jobmodel_disconnected_at.py +100 -0
  329. dstack/_internal/server/migrations/versions/22d74df9897e_add_events_and_event_targets.py +99 -0
  330. dstack/_internal/server/migrations/versions/23e01c56279a_make_blob_nullable.py +32 -0
  331. dstack/_internal/server/migrations/versions/2498ab323443_add_fleetmodel_consolidation_attempt_.py +44 -0
  332. dstack/_internal/server/migrations/versions/252d3743b641_.py +40 -0
  333. dstack/_internal/server/migrations/versions/25479f540245_add_probes.py +43 -0
  334. dstack/_internal/server/migrations/versions/27d3e55759fa_add_pools.py +152 -0
  335. dstack/_internal/server/migrations/versions/29826f417010_remove_instancemodel_retry_policy.py +34 -0
  336. dstack/_internal/server/migrations/versions/29c08c6a8cb3_.py +36 -0
  337. dstack/_internal/server/migrations/versions/35e90e1b0d3e_add_rolling_deployment_fields.py +42 -0
  338. dstack/_internal/server/migrations/versions/35f732ee4cf5_add_projectmodel_is_public.py +39 -0
  339. dstack/_internal/server/migrations/versions/3cf77fb8bcf1_store_repo_clone_url.py +85 -0
  340. dstack/_internal/server/migrations/versions/3d7f6c2ec000_add_jobmodel_registered.py +28 -0
  341. dstack/_internal/server/migrations/versions/3dbdce90d0e0_fix_code_uq_constraint.py +33 -0
  342. dstack/_internal/server/migrations/versions/48ad3ecbaea2_do_not_delete_projects_and_runs.py +46 -0
  343. dstack/_internal/server/migrations/versions/4ae1a5b0e7f1_add_run_list_index.py +34 -0
  344. dstack/_internal/server/migrations/versions/4b4319398164_introduce_runs_processing.py +144 -0
  345. dstack/_internal/server/migrations/versions/50dd7ea98639_index_status_columns.py +55 -0
  346. dstack/_internal/server/migrations/versions/51d45659d574_add_instancemodel_blocks_fields.py +43 -0
  347. dstack/_internal/server/migrations/versions/54a77e19c64c_add_manager_project_role.py +67 -0
  348. dstack/_internal/server/migrations/versions/555138b1f77f_change_instancemodel_for_asynchronous_.py +61 -0
  349. dstack/_internal/server/migrations/versions/58aa5162dcc3_add_gatewaymodel_configuration.py +32 -0
  350. dstack/_internal/server/migrations/versions/5ad8debc8fe6_fixes_for_psql.py +329 -0
  351. dstack/_internal/server/migrations/versions/5ec538b70e71_replace_instansestatus.py +31 -0
  352. dstack/_internal/server/migrations/versions/5f1707c525d2_add_filearchivemodel.py +39 -0
  353. dstack/_internal/server/migrations/versions/5fd659afca82_add_ix_instances_fleet_id.py +31 -0
  354. dstack/_internal/server/migrations/versions/60e444118b6d_add_jobprometheusmetrics.py +40 -0
  355. dstack/_internal/server/migrations/versions/63c3f19cb184_add_jobterminationreason_inactivity_.py +83 -0
  356. dstack/_internal/server/migrations/versions/644b8a114187_add_secretmodel.py +49 -0
  357. dstack/_internal/server/migrations/versions/686fb8341ea5_add_user_emails.py +32 -0
  358. dstack/_internal/server/migrations/versions/6c1a9d6530ee_add_jobmodel_exit_status.py +26 -0
  359. dstack/_internal/server/migrations/versions/706e0acc3a7d_add_runmodel_desired_replica_counts.py +26 -0
  360. dstack/_internal/server/migrations/versions/710e5b3fac8f_add_encryption.py +54 -0
  361. dstack/_internal/server/migrations/versions/728b1488b1b4_add_instance_health.py +50 -0
  362. dstack/_internal/server/migrations/versions/74a1f55209bd_store_enums_as_strings.py +484 -0
  363. dstack/_internal/server/migrations/versions/7b24b1c8eba7_add_instancemodel_last_processed_at.py +68 -0
  364. dstack/_internal/server/migrations/versions/7ba3b59d7ca6_add_runmodel_resubmission_attempt.py +35 -0
  365. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  366. dstack/_internal/server/migrations/versions/7d1ec2b920ac_add_computegroupmodel.py +91 -0
  367. dstack/_internal/server/migrations/versions/803c7e9ed85d_add_jobmodel_job_runtime_data.py +32 -0
  368. dstack/_internal/server/migrations/versions/82b32a135ea2_.py +58 -0
  369. dstack/_internal/server/migrations/versions/866ec1d67184_replace_retrypolicy_limit_with_.py +93 -0
  370. dstack/_internal/server/migrations/versions/903c91e24634_add_instances_termination_reason_message.py +34 -0
  371. dstack/_internal/server/migrations/versions/91a12fff6c76_add_repocredsmodel.py +43 -0
  372. dstack/_internal/server/migrations/versions/91ac5e543037_extend_repos_creds_column.py +36 -0
  373. dstack/_internal/server/migrations/versions/98cd9c8b5927_add_volumemodel.py +73 -0
  374. dstack/_internal/server/migrations/versions/98d1b92988bc_add_jobterminationreason_terminated_due_.py +140 -0
  375. dstack/_internal/server/migrations/versions/99b4c8c954ea_add_termination_reason_message.py +71 -0
  376. dstack/_internal/server/migrations/versions/9eea6af28e10_added_fail_reason_for_instancemodel.py +36 -0
  377. dstack/_internal/server/migrations/versions/__init__.py +0 -0
  378. dstack/_internal/server/migrations/versions/a060e2440936_.py +206 -0
  379. dstack/_internal/server/migrations/versions/a751ef183f27_move_attachment_data_to_volumes_.py +34 -0
  380. dstack/_internal/server/migrations/versions/a7b46c073fa1_add_placementgroupmodel.py +58 -0
  381. dstack/_internal/server/migrations/versions/afbc600ff2b2_add_created_at_to_usermodel_and_.py +102 -0
  382. dstack/_internal/server/migrations/versions/b4d6ad60db08_add_instancemodel_unreachable.py +37 -0
  383. dstack/_internal/server/migrations/versions/b88d55c2a07d_replace_instancestatus_ready.py +21 -0
  384. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  385. dstack/_internal/server/migrations/versions/bca2fdf130bf_add_runmodel_priority.py +34 -0
  386. dstack/_internal/server/migrations/versions/bfba43f6def2_.py +32 -0
  387. dstack/_internal/server/migrations/versions/c00090eaef21_support_fleets.py +108 -0
  388. dstack/_internal/server/migrations/versions/c154eece89da_add_fields_for_async_gateway_creation.py +74 -0
  389. dstack/_internal/server/migrations/versions/c20626d03cfb_add_jobmetricspoint.py +43 -0
  390. dstack/_internal/server/migrations/versions/c48df7985d57_add_instance_termination_retries.py +38 -0
  391. dstack/_internal/server/migrations/versions/c83d45f9a971_replace_string_with_text.py +150 -0
  392. dstack/_internal/server/migrations/versions/d0bb68e48b9f_add_project_owners_and_quotas.py +106 -0
  393. dstack/_internal/server/migrations/versions/d3e8af4786fa_gateway_compute_flag_deleted.py +34 -0
  394. dstack/_internal/server/migrations/versions/d4d9dc26cf58_add_ix_jobs_run_id.py +31 -0
  395. dstack/_internal/server/migrations/versions/d5863798bf41_add_volumemodel_last_job_processed_at.py +40 -0
  396. dstack/_internal/server/migrations/versions/d6b11105f659_add_usermodel_active.py +36 -0
  397. dstack/_internal/server/migrations/versions/da574e93fee0_add_jobmodel_volumes_detached_at.py +40 -0
  398. dstack/_internal/server/migrations/versions/dfffd6a1165c_add_fields_for_gateways_behind_alb.py +36 -0
  399. dstack/_internal/server/migrations/versions/e2d08cd1b8d9_add_jobmodel_fleet.py +41 -0
  400. dstack/_internal/server/migrations/versions/e3b7db07727f_add_gatewaycomputemodel_app_updated_at.py +61 -0
  401. dstack/_internal/server/migrations/versions/e6391ca6a264_separate_gateways_from_compute.py +72 -0
  402. dstack/_internal/server/migrations/versions/ea60480f82bb_add_membermodel_member_num.py +32 -0
  403. dstack/_internal/server/migrations/versions/ec02a26a256c_add_runmodel_next_triggered_at.py +38 -0
  404. dstack/_internal/server/migrations/versions/ed0ca30e13bb_migrate_instancestatus_provisioning.py +29 -0
  405. dstack/_internal/server/migrations/versions/fe72c4de8376_add_gateways.py +81 -0
  406. dstack/_internal/server/migrations/versions/ff1d94f65b08_user_ssh_key.py +34 -0
  407. dstack/_internal/server/migrations/versions/ffa99edd1988_add_jobterminationreason_max_duration_.py +81 -0
  408. dstack/_internal/server/models.py +930 -0
  409. dstack/_internal/server/routers/__init__.py +0 -0
  410. dstack/_internal/server/routers/auth.py +34 -0
  411. dstack/_internal/server/routers/backends.py +142 -0
  412. dstack/_internal/server/routers/events.py +60 -0
  413. dstack/_internal/server/routers/files.py +68 -0
  414. dstack/_internal/server/routers/fleets.py +202 -0
  415. dstack/_internal/server/routers/gateways.py +109 -0
  416. dstack/_internal/server/routers/gpus.py +32 -0
  417. dstack/_internal/server/routers/instances.py +77 -0
  418. dstack/_internal/server/routers/logs.py +34 -0
  419. dstack/_internal/server/routers/metrics.py +82 -0
  420. dstack/_internal/server/routers/projects.py +205 -0
  421. dstack/_internal/server/routers/prometheus.py +35 -0
  422. dstack/_internal/server/routers/repos.py +118 -0
  423. dstack/_internal/server/routers/runs.py +216 -0
  424. dstack/_internal/server/routers/secrets.py +86 -0
  425. dstack/_internal/server/routers/server.py +19 -0
  426. dstack/_internal/server/routers/users.py +158 -0
  427. dstack/_internal/server/routers/volumes.py +122 -0
  428. dstack/_internal/server/schemas/__init__.py +0 -0
  429. dstack/_internal/server/schemas/auth.py +83 -0
  430. dstack/_internal/server/schemas/backends.py +16 -0
  431. dstack/_internal/server/schemas/common.py +9 -0
  432. dstack/_internal/server/schemas/events.py +211 -0
  433. dstack/_internal/server/schemas/files.py +5 -0
  434. dstack/_internal/server/schemas/fleets.py +49 -0
  435. dstack/_internal/server/schemas/gateways.py +31 -0
  436. dstack/_internal/server/schemas/gpus.py +26 -0
  437. dstack/_internal/server/schemas/health/__init__.py +0 -0
  438. dstack/_internal/server/schemas/health/dcgm.py +56 -0
  439. dstack/_internal/server/schemas/instances.py +47 -0
  440. dstack/_internal/server/schemas/logs.py +17 -0
  441. dstack/_internal/server/schemas/projects.py +81 -0
  442. dstack/_internal/server/schemas/repos.py +24 -0
  443. dstack/_internal/server/schemas/runner.py +269 -0
  444. dstack/_internal/server/schemas/runs.py +66 -0
  445. dstack/_internal/server/schemas/secrets.py +16 -0
  446. dstack/_internal/server/schemas/users.py +72 -0
  447. dstack/_internal/server/schemas/volumes.py +29 -0
  448. dstack/_internal/server/security/__init__.py +0 -0
  449. dstack/_internal/server/security/permissions.py +251 -0
  450. dstack/_internal/server/services/__init__.py +0 -0
  451. dstack/_internal/server/services/auth.py +77 -0
  452. dstack/_internal/server/services/backends/__init__.py +404 -0
  453. dstack/_internal/server/services/backends/handlers.py +105 -0
  454. dstack/_internal/server/services/compute_groups.py +22 -0
  455. dstack/_internal/server/services/config.py +279 -0
  456. dstack/_internal/server/services/docker.py +162 -0
  457. dstack/_internal/server/services/encryption/__init__.py +102 -0
  458. dstack/_internal/server/services/encryption/keys/__init__.py +0 -0
  459. dstack/_internal/server/services/encryption/keys/aes.py +68 -0
  460. dstack/_internal/server/services/encryption/keys/base.py +19 -0
  461. dstack/_internal/server/services/encryption/keys/identity.py +28 -0
  462. dstack/_internal/server/services/events.py +477 -0
  463. dstack/_internal/server/services/files.py +91 -0
  464. dstack/_internal/server/services/fleets.py +1224 -0
  465. dstack/_internal/server/services/gateways/__init__.py +686 -0
  466. dstack/_internal/server/services/gateways/client.py +209 -0
  467. dstack/_internal/server/services/gateways/connection.py +139 -0
  468. dstack/_internal/server/services/gateways/pool.py +58 -0
  469. dstack/_internal/server/services/gpus.py +387 -0
  470. dstack/_internal/server/services/instances.py +731 -0
  471. dstack/_internal/server/services/jobs/__init__.py +840 -0
  472. dstack/_internal/server/services/jobs/configurators/__init__.py +0 -0
  473. dstack/_internal/server/services/jobs/configurators/base.py +469 -0
  474. dstack/_internal/server/services/jobs/configurators/dev.py +69 -0
  475. dstack/_internal/server/services/jobs/configurators/extensions/__init__.py +0 -0
  476. dstack/_internal/server/services/jobs/configurators/extensions/base.py +15 -0
  477. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  478. dstack/_internal/server/services/jobs/configurators/extensions/vscode.py +42 -0
  479. dstack/_internal/server/services/jobs/configurators/extensions/windsurf.py +43 -0
  480. dstack/_internal/server/services/jobs/configurators/service.py +28 -0
  481. dstack/_internal/server/services/jobs/configurators/task.py +39 -0
  482. dstack/_internal/server/services/locking.py +187 -0
  483. dstack/_internal/server/services/logging.py +29 -0
  484. dstack/_internal/server/services/logs/__init__.py +122 -0
  485. dstack/_internal/server/services/logs/aws.py +373 -0
  486. dstack/_internal/server/services/logs/base.py +47 -0
  487. dstack/_internal/server/services/logs/filelog.py +261 -0
  488. dstack/_internal/server/services/logs/fluentbit.py +329 -0
  489. dstack/_internal/server/services/logs/gcp.py +181 -0
  490. dstack/_internal/server/services/metrics.py +172 -0
  491. dstack/_internal/server/services/offers.py +249 -0
  492. dstack/_internal/server/services/permissions.py +37 -0
  493. dstack/_internal/server/services/placement.py +234 -0
  494. dstack/_internal/server/services/plugins.py +109 -0
  495. dstack/_internal/server/services/probes.py +10 -0
  496. dstack/_internal/server/services/projects.py +835 -0
  497. dstack/_internal/server/services/prometheus/__init__.py +0 -0
  498. dstack/_internal/server/services/prometheus/client_metrics.py +55 -0
  499. dstack/_internal/server/services/prometheus/custom_metrics.py +327 -0
  500. dstack/_internal/server/services/proxy/__init__.py +3 -0
  501. dstack/_internal/server/services/proxy/auth.py +12 -0
  502. dstack/_internal/server/services/proxy/deps.py +18 -0
  503. dstack/_internal/server/services/proxy/repo.py +189 -0
  504. dstack/_internal/server/services/proxy/routers/__init__.py +0 -0
  505. dstack/_internal/server/services/proxy/routers/service_proxy.py +49 -0
  506. dstack/_internal/server/services/proxy/services/__init__.py +0 -0
  507. dstack/_internal/server/services/proxy/services/service_proxy.py +135 -0
  508. dstack/_internal/server/services/repos.py +362 -0
  509. dstack/_internal/server/services/requirements/__init__.py +0 -0
  510. dstack/_internal/server/services/requirements/combine.py +260 -0
  511. dstack/_internal/server/services/resources.py +21 -0
  512. dstack/_internal/server/services/runner/__init__.py +0 -0
  513. dstack/_internal/server/services/runner/client.py +646 -0
  514. dstack/_internal/server/services/runner/ssh.py +128 -0
  515. dstack/_internal/server/services/runs/__init__.py +1026 -0
  516. dstack/_internal/server/services/runs/plan.py +703 -0
  517. dstack/_internal/server/services/runs/replicas.py +317 -0
  518. dstack/_internal/server/services/runs/spec.py +191 -0
  519. dstack/_internal/server/services/secrets.py +245 -0
  520. dstack/_internal/server/services/services/__init__.py +345 -0
  521. dstack/_internal/server/services/services/autoscalers.py +140 -0
  522. dstack/_internal/server/services/services/options.py +53 -0
  523. dstack/_internal/server/services/ssh.py +67 -0
  524. dstack/_internal/server/services/storage/__init__.py +37 -0
  525. dstack/_internal/server/services/storage/base.py +48 -0
  526. dstack/_internal/server/services/storage/gcs.py +66 -0
  527. dstack/_internal/server/services/storage/s3.py +69 -0
  528. dstack/_internal/server/services/users.py +461 -0
  529. dstack/_internal/server/services/volumes.py +496 -0
  530. dstack/_internal/server/settings.py +161 -0
  531. dstack/_internal/server/statics/00a6e1fb461ed2929fb9.png +0 -0
  532. dstack/_internal/server/statics/0cae4d9f0a36034984a7.png +0 -0
  533. dstack/_internal/server/statics/391de232cc0e30cae513.png +0 -0
  534. dstack/_internal/server/statics/4e0eead8c1a73689ef9d.svg +1 -0
  535. dstack/_internal/server/statics/544afa2f63428c2235b0.png +0 -0
  536. dstack/_internal/server/statics/54a4f50f74c6b9381530.svg +7 -0
  537. dstack/_internal/server/statics/68dd1360a7d2611e0132.svg +4 -0
  538. dstack/_internal/server/statics/69544b4c81973b54a66f.png +0 -0
  539. dstack/_internal/server/statics/77a8b02b17af19e39266.png +0 -0
  540. dstack/_internal/server/statics/83a93a8871c219104367.svg +9 -0
  541. dstack/_internal/server/statics/8f28bb8e9999e5e6a48b.svg +4 -0
  542. dstack/_internal/server/statics/9124086961ab8c366bc4.svg +9 -0
  543. dstack/_internal/server/statics/9a9ebaeb54b025dbac0a.svg +5 -0
  544. dstack/_internal/server/statics/a3428392dc534f3b15c4.svg +7 -0
  545. dstack/_internal/server/statics/ae22625574d69361f72c.png +0 -0
  546. dstack/_internal/server/statics/assets/android-chrome-144x144.png +0 -0
  547. dstack/_internal/server/statics/assets/android-chrome-192x192.png +0 -0
  548. dstack/_internal/server/statics/assets/android-chrome-256x256.png +0 -0
  549. dstack/_internal/server/statics/assets/android-chrome-36x36.png +0 -0
  550. dstack/_internal/server/statics/assets/android-chrome-384x384.png +0 -0
  551. dstack/_internal/server/statics/assets/android-chrome-48x48.png +0 -0
  552. dstack/_internal/server/statics/assets/android-chrome-512x512.png +0 -0
  553. dstack/_internal/server/statics/assets/android-chrome-72x72.png +0 -0
  554. dstack/_internal/server/statics/assets/android-chrome-96x96.png +0 -0
  555. dstack/_internal/server/statics/assets/apple-touch-icon-1024x1024.png +0 -0
  556. dstack/_internal/server/statics/assets/apple-touch-icon-114x114.png +0 -0
  557. dstack/_internal/server/statics/assets/apple-touch-icon-120x120.png +0 -0
  558. dstack/_internal/server/statics/assets/apple-touch-icon-144x144.png +0 -0
  559. dstack/_internal/server/statics/assets/apple-touch-icon-152x152.png +0 -0
  560. dstack/_internal/server/statics/assets/apple-touch-icon-167x167.png +0 -0
  561. dstack/_internal/server/statics/assets/apple-touch-icon-180x180.png +0 -0
  562. dstack/_internal/server/statics/assets/apple-touch-icon-57x57.png +0 -0
  563. dstack/_internal/server/statics/assets/apple-touch-icon-60x60.png +0 -0
  564. dstack/_internal/server/statics/assets/apple-touch-icon-72x72.png +0 -0
  565. dstack/_internal/server/statics/assets/apple-touch-icon-76x76.png +0 -0
  566. dstack/_internal/server/statics/assets/apple-touch-icon-precomposed.png +0 -0
  567. dstack/_internal/server/statics/assets/apple-touch-icon.png +0 -0
  568. dstack/_internal/server/statics/assets/apple-touch-startup-image-1125x2436.png +0 -0
  569. dstack/_internal/server/statics/assets/apple-touch-startup-image-1136x640.png +0 -0
  570. dstack/_internal/server/statics/assets/apple-touch-startup-image-1170x2532.png +0 -0
  571. dstack/_internal/server/statics/assets/apple-touch-startup-image-1179x2556.png +0 -0
  572. dstack/_internal/server/statics/assets/apple-touch-startup-image-1242x2208.png +0 -0
  573. dstack/_internal/server/statics/assets/apple-touch-startup-image-1242x2688.png +0 -0
  574. dstack/_internal/server/statics/assets/apple-touch-startup-image-1284x2778.png +0 -0
  575. dstack/_internal/server/statics/assets/apple-touch-startup-image-1290x2796.png +0 -0
  576. dstack/_internal/server/statics/assets/apple-touch-startup-image-1334x750.png +0 -0
  577. dstack/_internal/server/statics/assets/apple-touch-startup-image-1488x2266.png +0 -0
  578. dstack/_internal/server/statics/assets/apple-touch-startup-image-1536x2048.png +0 -0
  579. dstack/_internal/server/statics/assets/apple-touch-startup-image-1620x2160.png +0 -0
  580. dstack/_internal/server/statics/assets/apple-touch-startup-image-1640x2160.png +0 -0
  581. dstack/_internal/server/statics/assets/apple-touch-startup-image-1668x2224.png +0 -0
  582. dstack/_internal/server/statics/assets/apple-touch-startup-image-1668x2388.png +0 -0
  583. dstack/_internal/server/statics/assets/apple-touch-startup-image-1792x828.png +0 -0
  584. dstack/_internal/server/statics/assets/apple-touch-startup-image-2048x1536.png +0 -0
  585. dstack/_internal/server/statics/assets/apple-touch-startup-image-2048x2732.png +0 -0
  586. dstack/_internal/server/statics/assets/apple-touch-startup-image-2160x1620.png +0 -0
  587. dstack/_internal/server/statics/assets/apple-touch-startup-image-2160x1640.png +0 -0
  588. dstack/_internal/server/statics/assets/apple-touch-startup-image-2208x1242.png +0 -0
  589. dstack/_internal/server/statics/assets/apple-touch-startup-image-2224x1668.png +0 -0
  590. dstack/_internal/server/statics/assets/apple-touch-startup-image-2266x1488.png +0 -0
  591. dstack/_internal/server/statics/assets/apple-touch-startup-image-2388x1668.png +0 -0
  592. dstack/_internal/server/statics/assets/apple-touch-startup-image-2436x1125.png +0 -0
  593. dstack/_internal/server/statics/assets/apple-touch-startup-image-2532x1170.png +0 -0
  594. dstack/_internal/server/statics/assets/apple-touch-startup-image-2556x1179.png +0 -0
  595. dstack/_internal/server/statics/assets/apple-touch-startup-image-2688x1242.png +0 -0
  596. dstack/_internal/server/statics/assets/apple-touch-startup-image-2732x2048.png +0 -0
  597. dstack/_internal/server/statics/assets/apple-touch-startup-image-2778x1284.png +0 -0
  598. dstack/_internal/server/statics/assets/apple-touch-startup-image-2796x1290.png +0 -0
  599. dstack/_internal/server/statics/assets/apple-touch-startup-image-640x1136.png +0 -0
  600. dstack/_internal/server/statics/assets/apple-touch-startup-image-750x1334.png +0 -0
  601. dstack/_internal/server/statics/assets/apple-touch-startup-image-828x1792.png +0 -0
  602. dstack/_internal/server/statics/assets/browserconfig.xml +12 -0
  603. dstack/_internal/server/statics/assets/favicon-16x16.png +0 -0
  604. dstack/_internal/server/statics/assets/favicon-32x32.png +0 -0
  605. dstack/_internal/server/statics/assets/favicon-48x48.png +0 -0
  606. dstack/_internal/server/statics/assets/favicon.ico +0 -0
  607. dstack/{dashboard/statics/assets/manifest.json → _internal/server/statics/assets/manifest.webmanifest} +18 -9
  608. dstack/_internal/server/statics/assets/mstile-144x144.png +0 -0
  609. dstack/_internal/server/statics/assets/mstile-150x150.png +0 -0
  610. dstack/_internal/server/statics/assets/mstile-310x150.png +0 -0
  611. dstack/_internal/server/statics/assets/mstile-310x310.png +0 -0
  612. dstack/_internal/server/statics/assets/mstile-70x70.png +0 -0
  613. dstack/_internal/server/statics/assets/yandex-browser-50x50.png +0 -0
  614. dstack/_internal/server/statics/b7ae68f44193474fc578.png +0 -0
  615. dstack/_internal/server/statics/d2f008c75b2b5b191f3f.png +0 -0
  616. dstack/_internal/server/statics/d44c33e1b92e05c379fd.png +0 -0
  617. dstack/_internal/server/statics/dd43ff0552815179d7ab.png +0 -0
  618. dstack/_internal/server/statics/dd4e7166c0b9aac197d7.png +0 -0
  619. dstack/_internal/server/statics/e30b27916930d43d2271.png +0 -0
  620. dstack/_internal/server/statics/e467d7d60aae81ab198b.svg +6 -0
  621. dstack/_internal/server/statics/eb9b344b73818fe2b71a.png +0 -0
  622. dstack/_internal/server/statics/f517dd626eb964120de0.png +0 -0
  623. dstack/_internal/server/statics/f958aecddee5d8e3222c.png +0 -0
  624. dstack/_internal/server/statics/index.html +3 -0
  625. dstack/_internal/server/statics/logo-notext.svg +116 -0
  626. dstack/_internal/server/statics/main-2e6967bad9f29395eea6.css +3 -0
  627. dstack/_internal/server/statics/main-7dc0f6d20b8b41659acc.js +155547 -0
  628. dstack/_internal/server/statics/main-7dc0f6d20b8b41659acc.js.map +1 -0
  629. dstack/{dashboard → _internal/server}/statics/manifest.json +2 -2
  630. dstack/_internal/server/statics/static/media/entraID.d65d1f3e9486a8e56d24fc07b3230885.svg +9 -0
  631. dstack/_internal/server/statics/static/media/google.b194b06fafd0a52aeb566922160ea514.svg +1 -0
  632. dstack/{dashboard/statics/static/media/logo.f9d7170678f68f796e270698633770ec.svg → _internal/server/statics/static/media/logo.f602feeb138844eda97c8cb641461448.svg} +8 -6
  633. dstack/_internal/server/statics/static/media/okta.12f178e6873a1100965f2a4dbd18fcec.svg +2 -0
  634. dstack/_internal/server/statics/static/media/theme.3994c817bb7dda191c1c9640dee0bf42.svg +3 -0
  635. dstack/_internal/server/testing/__init__.py +0 -0
  636. dstack/_internal/server/testing/common.py +1220 -0
  637. dstack/_internal/server/testing/conf.py +53 -0
  638. dstack/_internal/server/testing/matchers.py +31 -0
  639. dstack/_internal/server/utils/__init__.py +0 -0
  640. dstack/_internal/server/utils/common.py +55 -0
  641. dstack/_internal/server/utils/logging.py +51 -0
  642. dstack/_internal/server/utils/provisioning.py +368 -0
  643. dstack/_internal/server/utils/routers.py +166 -0
  644. dstack/_internal/server/utils/sentry_utils.py +24 -0
  645. dstack/_internal/settings.py +49 -0
  646. dstack/_internal/utils/__init__.py +0 -0
  647. dstack/_internal/utils/common.py +318 -0
  648. dstack/_internal/utils/cron.py +5 -0
  649. dstack/_internal/utils/crypto.py +40 -0
  650. dstack/_internal/utils/env.py +88 -0
  651. dstack/_internal/utils/event_loop.py +30 -0
  652. dstack/_internal/utils/files.py +69 -0
  653. dstack/_internal/utils/gpu.py +59 -0
  654. dstack/_internal/utils/hash.py +31 -0
  655. dstack/_internal/utils/interpolator.py +91 -0
  656. dstack/_internal/utils/json_schema.py +11 -0
  657. dstack/_internal/utils/json_utils.py +54 -0
  658. dstack/_internal/utils/logging.py +5 -0
  659. dstack/_internal/utils/nested_list.py +47 -0
  660. dstack/_internal/utils/network.py +50 -0
  661. dstack/_internal/utils/path.py +57 -0
  662. dstack/_internal/utils/random_names.py +258 -0
  663. dstack/_internal/utils/ssh.py +346 -0
  664. dstack/_internal/utils/tags.py +42 -0
  665. dstack/_internal/utils/typing.py +14 -0
  666. dstack/_internal/utils/version.py +22 -0
  667. dstack/api/__init__.py +46 -0
  668. dstack/api/_public/__init__.py +96 -0
  669. dstack/api/_public/backends.py +42 -0
  670. dstack/api/_public/common.py +5 -0
  671. dstack/api/_public/repos.py +202 -0
  672. dstack/api/_public/runs.py +714 -0
  673. dstack/api/server/__init__.py +206 -0
  674. dstack/api/server/_auth.py +30 -0
  675. dstack/api/server/_backends.py +38 -0
  676. dstack/api/server/_events.py +64 -0
  677. dstack/api/server/_files.py +18 -0
  678. dstack/api/server/_fleets.py +82 -0
  679. dstack/api/server/_gateways.py +54 -0
  680. dstack/api/server/_gpus.py +27 -0
  681. dstack/api/server/_group.py +22 -0
  682. dstack/api/server/_logs.py +15 -0
  683. dstack/api/server/_metrics.py +23 -0
  684. dstack/api/server/_projects.py +124 -0
  685. dstack/api/server/_repos.py +64 -0
  686. dstack/api/server/_runs.py +102 -0
  687. dstack/api/server/_secrets.py +36 -0
  688. dstack/api/server/_users.py +82 -0
  689. dstack/api/server/_volumes.py +39 -0
  690. dstack/api/server/utils.py +34 -0
  691. dstack/api/utils.py +105 -0
  692. dstack/core/__init__.py +0 -0
  693. dstack/plugins/__init__.py +8 -0
  694. dstack/plugins/_base.py +72 -0
  695. dstack/plugins/_models.py +8 -0
  696. dstack/plugins/_utils.py +19 -0
  697. dstack/plugins/builtin/__init__.py +0 -0
  698. dstack/plugins/builtin/rest_plugin/__init__.py +18 -0
  699. dstack/plugins/builtin/rest_plugin/_models.py +48 -0
  700. dstack/plugins/builtin/rest_plugin/_plugin.py +147 -0
  701. dstack/version.py +3 -1
  702. dstack-0.20.7.dist-info/METADATA +519 -0
  703. dstack-0.20.7.dist-info/RECORD +720 -0
  704. {dstack-0.0.9.dist-info → dstack-0.20.7.dist-info}/WHEEL +1 -2
  705. dstack-0.20.7.dist-info/entry_points.txt +2 -0
  706. dstack-0.20.7.dist-info/licenses/LICENSE.md +353 -0
  707. dstack/aws/__init__.py +0 -180
  708. dstack/aws/artifacts.py +0 -111
  709. dstack/aws/config.py +0 -40
  710. dstack/aws/jobs.py +0 -245
  711. dstack/aws/logs.py +0 -186
  712. dstack/aws/repos.py +0 -137
  713. dstack/aws/run_names.py +0 -17
  714. dstack/aws/runners.py +0 -693
  715. dstack/aws/runs.py +0 -79
  716. dstack/aws/secrets.py +0 -99
  717. dstack/aws/tags.py +0 -138
  718. dstack/backend.py +0 -299
  719. dstack/cli/app.py +0 -41
  720. dstack/cli/artifacts.py +0 -87
  721. dstack/cli/common.py +0 -57
  722. dstack/cli/config.py +0 -194
  723. dstack/cli/dashboard.py +0 -26
  724. dstack/cli/delete.py +0 -49
  725. dstack/cli/init.py +0 -33
  726. dstack/cli/logs.py +0 -87
  727. dstack/cli/main.py +0 -81
  728. dstack/cli/restart.py +0 -43
  729. dstack/cli/run.py +0 -223
  730. dstack/cli/schema.py +0 -46
  731. dstack/cli/secrets.py +0 -97
  732. dstack/cli/status.py +0 -140
  733. dstack/cli/stop.py +0 -53
  734. dstack/cli/tags.py +0 -100
  735. dstack/config.py +0 -80
  736. dstack/dashboard/artifacts.py +0 -26
  737. dstack/dashboard/logs.py +0 -73
  738. dstack/dashboard/main.py +0 -45
  739. dstack/dashboard/repos.py +0 -41
  740. dstack/dashboard/runs.py +0 -140
  741. dstack/dashboard/secrets.py +0 -53
  742. dstack/dashboard/statics/4d6a4e032505c1efd23c.png +0 -0
  743. dstack/dashboard/statics/7e018c3e5566d7c349a8.png +0 -0
  744. dstack/dashboard/statics/assets/android-chrome-144x144.png +0 -0
  745. dstack/dashboard/statics/assets/android-chrome-192x192.png +0 -0
  746. dstack/dashboard/statics/assets/android-chrome-256x256.png +0 -0
  747. dstack/dashboard/statics/assets/android-chrome-36x36.png +0 -0
  748. dstack/dashboard/statics/assets/android-chrome-384x384.png +0 -0
  749. dstack/dashboard/statics/assets/android-chrome-48x48.png +0 -0
  750. dstack/dashboard/statics/assets/android-chrome-512x512.png +0 -0
  751. dstack/dashboard/statics/assets/android-chrome-72x72.png +0 -0
  752. dstack/dashboard/statics/assets/android-chrome-96x96.png +0 -0
  753. dstack/dashboard/statics/assets/apple-touch-icon-1024x1024.png +0 -0
  754. dstack/dashboard/statics/assets/apple-touch-icon-114x114.png +0 -0
  755. dstack/dashboard/statics/assets/apple-touch-icon-120x120.png +0 -0
  756. dstack/dashboard/statics/assets/apple-touch-icon-144x144.png +0 -0
  757. dstack/dashboard/statics/assets/apple-touch-icon-152x152.png +0 -0
  758. dstack/dashboard/statics/assets/apple-touch-icon-167x167.png +0 -0
  759. dstack/dashboard/statics/assets/apple-touch-icon-180x180.png +0 -0
  760. dstack/dashboard/statics/assets/apple-touch-icon-57x57.png +0 -0
  761. dstack/dashboard/statics/assets/apple-touch-icon-60x60.png +0 -0
  762. dstack/dashboard/statics/assets/apple-touch-icon-72x72.png +0 -0
  763. dstack/dashboard/statics/assets/apple-touch-icon-76x76.png +0 -0
  764. dstack/dashboard/statics/assets/apple-touch-icon-precomposed.png +0 -0
  765. dstack/dashboard/statics/assets/apple-touch-icon.png +0 -0
  766. dstack/dashboard/statics/assets/apple-touch-startup-image-1125x2436.png +0 -0
  767. dstack/dashboard/statics/assets/apple-touch-startup-image-1136x640.png +0 -0
  768. dstack/dashboard/statics/assets/apple-touch-startup-image-1242x2208.png +0 -0
  769. dstack/dashboard/statics/assets/apple-touch-startup-image-1242x2688.png +0 -0
  770. dstack/dashboard/statics/assets/apple-touch-startup-image-1334x750.png +0 -0
  771. dstack/dashboard/statics/assets/apple-touch-startup-image-1536x2048.png +0 -0
  772. dstack/dashboard/statics/assets/apple-touch-startup-image-1620x2160.png +0 -0
  773. dstack/dashboard/statics/assets/apple-touch-startup-image-1668x2224.png +0 -0
  774. dstack/dashboard/statics/assets/apple-touch-startup-image-1668x2388.png +0 -0
  775. dstack/dashboard/statics/assets/apple-touch-startup-image-1792x828.png +0 -0
  776. dstack/dashboard/statics/assets/apple-touch-startup-image-2048x1536.png +0 -0
  777. dstack/dashboard/statics/assets/apple-touch-startup-image-2048x2732.png +0 -0
  778. dstack/dashboard/statics/assets/apple-touch-startup-image-2160x1620.png +0 -0
  779. dstack/dashboard/statics/assets/apple-touch-startup-image-2208x1242.png +0 -0
  780. dstack/dashboard/statics/assets/apple-touch-startup-image-2224x1668.png +0 -0
  781. dstack/dashboard/statics/assets/apple-touch-startup-image-2388x1668.png +0 -0
  782. dstack/dashboard/statics/assets/apple-touch-startup-image-2436x1125.png +0 -0
  783. dstack/dashboard/statics/assets/apple-touch-startup-image-2688x1242.png +0 -0
  784. dstack/dashboard/statics/assets/apple-touch-startup-image-2732x2048.png +0 -0
  785. dstack/dashboard/statics/assets/apple-touch-startup-image-640x1136.png +0 -0
  786. dstack/dashboard/statics/assets/apple-touch-startup-image-750x1334.png +0 -0
  787. dstack/dashboard/statics/assets/apple-touch-startup-image-828x1792.png +0 -0
  788. dstack/dashboard/statics/assets/browserconfig.xml +0 -15
  789. dstack/dashboard/statics/assets/coast-228x228.png +0 -0
  790. dstack/dashboard/statics/assets/favicon-16x16.png +0 -0
  791. dstack/dashboard/statics/assets/favicon-32x32.png +0 -0
  792. dstack/dashboard/statics/assets/favicon-48x48.png +0 -0
  793. dstack/dashboard/statics/assets/favicon.ico +0 -0
  794. dstack/dashboard/statics/assets/firefox_app_128x128.png +0 -0
  795. dstack/dashboard/statics/assets/firefox_app_512x512.png +0 -0
  796. dstack/dashboard/statics/assets/firefox_app_60x60.png +0 -0
  797. dstack/dashboard/statics/assets/manifest.webapp +0 -14
  798. dstack/dashboard/statics/assets/mstile-144x144.png +0 -0
  799. dstack/dashboard/statics/assets/mstile-150x150.png +0 -0
  800. dstack/dashboard/statics/assets/mstile-310x150.png +0 -0
  801. dstack/dashboard/statics/assets/mstile-310x310.png +0 -0
  802. dstack/dashboard/statics/assets/mstile-70x70.png +0 -0
  803. dstack/dashboard/statics/assets/yandex-browser-50x50.png +0 -0
  804. dstack/dashboard/statics/d0f71e48806e25d72553.png +0 -0
  805. dstack/dashboard/statics/index.html +0 -7
  806. dstack/dashboard/statics/main-1d87e34eb0454da8ebb4.js +0 -3
  807. dstack/dashboard/statics/main-1d87e34eb0454da8ebb4.js.LICENSE.txt +0 -102
  808. dstack/dashboard/statics/main-1d87e34eb0454da8ebb4.js.map +0 -1
  809. dstack/dashboard/statics/main.css +0 -5058
  810. dstack/dashboard/statics/splash_thumbnail.png +0 -0
  811. dstack/dashboard/statics/static/media/check.3f68ffc787a15c0476793a6d18ecb71a.svg +0 -3
  812. dstack/dashboard/statics/static/media/chevron-down.bfd8f22c4a5db4d443e76bca3b02f334.svg +0 -3
  813. dstack/dashboard/statics/static/media/chevron-up.bade0c5d82d741cead615813264140c9.svg +0 -3
  814. dstack/dashboard/statics/static/media/clock.583b744f29b9d143718a55e7c35fe38e.svg +0 -3
  815. dstack/dashboard/statics/static/media/close.a8bb9e47361b03a3b5084dad676ba1da.svg +0 -3
  816. dstack/dashboard/statics/static/media/content-copy.73f5f2a175094757758e315243a4111e.svg +0 -3
  817. dstack/dashboard/statics/static/media/delete-outline.6a8abf4e4f9cb777781967efd56efe9b.svg +0 -3
  818. dstack/dashboard/statics/static/media/dots-vertical.82fc618192e0c7dc4d615ff93269246a.svg +0 -3
  819. dstack/dashboard/statics/static/media/earth.1ad57c7f59f4be5c8bb2fa00439c3149.svg +0 -3
  820. dstack/dashboard/statics/static/media/email.320bc3af24a5f1bb41ebd85f66a5dd70.svg +0 -3
  821. dstack/dashboard/statics/static/media/external-link.99b88e699c15afb820a1779d9a2261ed.svg +0 -3
  822. dstack/dashboard/statics/static/media/eye-off-outline.5b4afb7ad624a44dd307518ff93d1faa.svg +0 -3
  823. dstack/dashboard/statics/static/media/eye-outline.ca41708feaaed1edb15c5fff021fbafe.svg +0 -3
  824. dstack/dashboard/statics/static/media/file-download-outline.3634b41923ba79b297ff294ef898661c.svg +0 -3
  825. dstack/dashboard/statics/static/media/folder-outline.33378387af61821dd1207e4b2d061a07.svg +0 -3
  826. dstack/dashboard/statics/static/media/github-circle.1bb85d171c31a3c2eebad07319377171.svg +0 -3
  827. dstack/dashboard/statics/static/media/infinity.915f92939afc0a37f94adba211ceb172.svg +0 -3
  828. dstack/dashboard/statics/static/media/layers.b4b02cea267a617d7aa44c2719250c89.svg +0 -3
  829. dstack/dashboard/statics/static/media/linkedin.1c52fae553eee54397f0e63a79455a5e.svg +0 -3
  830. dstack/dashboard/statics/static/media/loading.e466be7b2c1f0ac9e7e51ca929d0e37d.svg +0 -3
  831. dstack/dashboard/statics/static/media/lock.4a4c7768d0fa60c716609ddc483470ef.svg +0 -3
  832. dstack/dashboard/statics/static/media/magnify.0c803314d039d21f3cb1504ccd1437a4.svg +0 -3
  833. dstack/dashboard/statics/static/media/mark.3f68ffc787a15c0476793a6d18ecb71a.svg +0 -3
  834. dstack/dashboard/statics/static/media/menu-close.3ee84714181017c6ff837830297c8437.svg +0 -3
  835. dstack/dashboard/statics/static/media/menu.922f81e0972fbcbb5adcd8def20c86a3.svg +0 -3
  836. dstack/dashboard/statics/static/media/pencil.f706a3b9dcbff4959a91bf72e1e6324f.svg +0 -3
  837. dstack/dashboard/statics/static/media/refresh.a80edb948e98b322cd73b67814a57a48.svg +0 -3
  838. dstack/dashboard/statics/static/media/shape-plus.63b093c7f4b44c3def774f30fcfbceca.svg +0 -3
  839. dstack/dashboard/statics/static/media/slack.ec2fca99c6b944950ac65404ddd26880.svg +0 -4
  840. dstack/dashboard/statics/static/media/small-logo.b9cc8d09f646a553e65fa336dafd8b10.svg +0 -116
  841. dstack/dashboard/statics/static/media/source-branch.b8d22cfc42a7bed81f0fc08130818e85.svg +0 -3
  842. dstack/dashboard/statics/static/media/source-commit.be2bb53c081b9b6836adffccc0b8d3e6.svg +0 -3
  843. dstack/dashboard/statics/static/media/stop.11488ff1437ad929476be8924a3b7075.svg +0 -3
  844. dstack/dashboard/statics/static/media/tag-minus.15680a815b0b8d027e973c84832c05e6.svg +0 -3
  845. dstack/dashboard/statics/static/media/tag-outline.19b0bf86a8afd7d6d9c716e9a91d94ca.svg +0 -3
  846. dstack/dashboard/statics/static/media/twitter.4af18861c84a2f3044c7546b55d5739c.svg +0 -3
  847. dstack/dashboard/tags.py +0 -119
  848. dstack/jobs.py +0 -255
  849. dstack/providers/__init__.py +0 -316
  850. dstack/providers/_python/main.py +0 -88
  851. dstack/providers/_tensorboard/main.py +0 -93
  852. dstack/providers/_torchrun/main.py +0 -121
  853. dstack/providers/bash/main.py +0 -90
  854. dstack/providers/code/main.py +0 -95
  855. dstack/providers/docker/main.py +0 -79
  856. dstack/providers/lab/main.py +0 -95
  857. dstack/providers/notebook/main.py +0 -90
  858. dstack/random_name.py +0 -29
  859. dstack/repo.py +0 -135
  860. dstack/runners.py +0 -35
  861. dstack/util.py +0 -15
  862. dstack-0.0.9.dist-info/METADATA +0 -176
  863. dstack-0.0.9.dist-info/RECORD +0 -179
  864. dstack-0.0.9.dist-info/entry_points.txt +0 -3
  865. dstack-0.0.9.dist-info/top_level.txt +0 -2
  866. tests/test_config.py +0 -70
  867. /dstack/{cli → _internal}/__init__.py +0 -0
  868. /dstack/{dashboard → _internal/cli}/__init__.py +0 -0
  869. /dstack/{providers/_python → _internal/cli/models}/__init__.py +0 -0
  870. /dstack/{providers/_tensorboard → _internal/cli/services}/__init__.py +0 -0
  871. /dstack/{providers/_torchrun → _internal/cli/utils}/__init__.py +0 -0
  872. /dstack/{providers/bash → _internal/core}/__init__.py +0 -0
  873. /dstack/{providers/code → _internal/core/backends}/__init__.py +0 -0
  874. /dstack/{providers/docker → _internal/core/backends/aws}/__init__.py +0 -0
  875. /dstack/{providers/lab → _internal/core/backends/azure}/__init__.py +0 -0
  876. /dstack/{providers/notebook → _internal/core/backends/base}/__init__.py +0 -0
  877. {tests → dstack/_internal/core/backends/cloudrift}/__init__.py +0 -0
  878. /dstack/{dashboard → _internal/server}/statics/assets/yandex-browser-manifest.json +0 -0
  879. /dstack/{dashboard → _internal/server}/statics/robots.txt +0 -0
@@ -0,0 +1,1224 @@
1
+ import uuid
2
+ from collections.abc import Callable
3
+ from datetime import datetime
4
+ from functools import wraps
5
+ from typing import List, Literal, Optional, Tuple, TypeVar, Union, cast
6
+
7
+ from sqlalchemy import and_, func, or_, select
8
+ from sqlalchemy.ext.asyncio import AsyncSession
9
+ from sqlalchemy.orm import aliased, joinedload, selectinload
10
+
11
+ from dstack._internal.core.backends.base.backend import Backend
12
+ from dstack._internal.core.backends.features import BACKENDS_WITH_CREATE_INSTANCE_SUPPORT
13
+ from dstack._internal.core.errors import (
14
+ ForbiddenError,
15
+ ResourceExistsError,
16
+ ServerClientError,
17
+ )
18
+ from dstack._internal.core.models.common import ApplyAction, CoreModel
19
+ from dstack._internal.core.models.envs import Env
20
+ from dstack._internal.core.models.fleets import (
21
+ ApplyFleetPlanInput,
22
+ Fleet,
23
+ FleetConfiguration,
24
+ FleetPlan,
25
+ FleetSpec,
26
+ FleetStatus,
27
+ InstanceGroupPlacement,
28
+ SSHHostParams,
29
+ SSHParams,
30
+ )
31
+ from dstack._internal.core.models.instances import (
32
+ InstanceOfferWithAvailability,
33
+ InstanceStatus,
34
+ InstanceTerminationReason,
35
+ RemoteConnectionInfo,
36
+ SSHConnectionParams,
37
+ SSHKey,
38
+ )
39
+ from dstack._internal.core.models.placement import PlacementGroup
40
+ from dstack._internal.core.models.profiles import (
41
+ Profile,
42
+ SpotPolicy,
43
+ )
44
+ from dstack._internal.core.models.projects import Project
45
+ from dstack._internal.core.models.resources import ResourcesSpec
46
+ from dstack._internal.core.models.runs import (
47
+ JobProvisioningData,
48
+ Requirements,
49
+ RunStatus,
50
+ get_policy_map,
51
+ )
52
+ from dstack._internal.core.models.users import GlobalRole
53
+ from dstack._internal.core.services import validate_dstack_resource_name
54
+ from dstack._internal.core.services.diff import ModelDiff, copy_model, diff_models
55
+ from dstack._internal.server.db import get_db, is_db_postgres, is_db_sqlite
56
+ from dstack._internal.server.models import (
57
+ FleetModel,
58
+ InstanceModel,
59
+ JobModel,
60
+ MemberModel,
61
+ ProjectModel,
62
+ RunModel,
63
+ UserModel,
64
+ )
65
+ from dstack._internal.server.services import events
66
+ from dstack._internal.server.services import instances as instances_services
67
+ from dstack._internal.server.services import offers as offers_services
68
+ from dstack._internal.server.services.instances import (
69
+ get_instance_remote_connection_info,
70
+ list_active_remote_instances,
71
+ switch_instance_status,
72
+ )
73
+ from dstack._internal.server.services.locking import (
74
+ get_locker,
75
+ string_to_lock_id,
76
+ )
77
+ from dstack._internal.server.services.plugins import apply_plugin_policies
78
+ from dstack._internal.server.services.projects import (
79
+ get_member,
80
+ get_member_permissions,
81
+ list_user_project_models,
82
+ project_model_to_project,
83
+ )
84
+ from dstack._internal.server.services.resources import set_resources_defaults
85
+ from dstack._internal.utils import random_names
86
+ from dstack._internal.utils.logging import get_logger
87
+ from dstack._internal.utils.ssh import pkey_from_str
88
+
89
+ logger = get_logger(__name__)
90
+
91
+
92
+ def switch_fleet_status(
93
+ session: AsyncSession,
94
+ fleet_model: FleetModel,
95
+ new_status: FleetStatus,
96
+ actor: events.AnyActor = events.SystemActor(),
97
+ ):
98
+ """
99
+ Switch fleet status.
100
+ """
101
+ old_status = fleet_model.status
102
+ if old_status == new_status:
103
+ return
104
+
105
+ fleet_model.status = new_status
106
+
107
+ msg = f"Fleet status changed {old_status.upper()} -> {new_status.upper()}"
108
+ events.emit(session, msg, actor=actor, targets=[events.Target.from_model(fleet_model)])
109
+
110
+
111
+ async def list_projects_with_no_active_fleets(
112
+ session: AsyncSession,
113
+ user: UserModel,
114
+ ) -> List[Project]:
115
+ """
116
+ Returns all projects where the user is a member that have no active fleets.
117
+
118
+ Active fleets are those with `deleted == False`. Projects with only deleted fleets
119
+ (or no fleets) are included. Deleted projects are excluded.
120
+
121
+ Applies to all users (both regular users and admins require membership).
122
+ """
123
+ active_fleet_alias = aliased(FleetModel)
124
+ member_alias = aliased(MemberModel)
125
+
126
+ query = (
127
+ select(ProjectModel)
128
+ .join(
129
+ member_alias,
130
+ and_(
131
+ member_alias.project_id == ProjectModel.id,
132
+ member_alias.user_id == user.id,
133
+ ),
134
+ )
135
+ .outerjoin(
136
+ active_fleet_alias,
137
+ and_(
138
+ active_fleet_alias.project_id == ProjectModel.id,
139
+ active_fleet_alias.deleted == False,
140
+ ),
141
+ )
142
+ .where(
143
+ ProjectModel.deleted == False,
144
+ active_fleet_alias.id.is_(None),
145
+ )
146
+ .order_by(ProjectModel.created_at)
147
+ )
148
+
149
+ res = await session.execute(query)
150
+ project_models = list(res.scalars().unique().all())
151
+
152
+ return [
153
+ project_model_to_project(p, include_backends=False, include_members=False)
154
+ for p in project_models
155
+ ]
156
+
157
+
158
+ async def list_fleets(
159
+ session: AsyncSession,
160
+ user: UserModel,
161
+ project_name: Optional[str],
162
+ only_active: bool,
163
+ prev_created_at: Optional[datetime],
164
+ prev_id: Optional[uuid.UUID],
165
+ limit: int,
166
+ ascending: bool,
167
+ ) -> List[Fleet]:
168
+ projects = await list_user_project_models(
169
+ session=session,
170
+ user=user,
171
+ only_names=True,
172
+ )
173
+ if project_name is not None:
174
+ projects = [p for p in projects if p.name == project_name]
175
+ fleet_models = await list_projects_fleet_models(
176
+ session=session,
177
+ projects=projects,
178
+ only_active=only_active,
179
+ prev_created_at=prev_created_at,
180
+ prev_id=prev_id,
181
+ limit=limit,
182
+ ascending=ascending,
183
+ )
184
+ return [fleet_model_to_fleet(v) for v in fleet_models]
185
+
186
+
187
+ async def list_projects_fleet_models(
188
+ session: AsyncSession,
189
+ projects: List[ProjectModel],
190
+ only_active: bool,
191
+ prev_created_at: Optional[datetime],
192
+ prev_id: Optional[uuid.UUID],
193
+ limit: int,
194
+ ascending: bool,
195
+ ) -> List[FleetModel]:
196
+ filters = []
197
+ filters.append(FleetModel.project_id.in_(p.id for p in projects))
198
+ if only_active:
199
+ filters.append(FleetModel.deleted == False)
200
+ if prev_created_at is not None:
201
+ if ascending:
202
+ if prev_id is None:
203
+ filters.append(FleetModel.created_at > prev_created_at)
204
+ else:
205
+ filters.append(
206
+ or_(
207
+ FleetModel.created_at > prev_created_at,
208
+ and_(FleetModel.created_at == prev_created_at, FleetModel.id < prev_id),
209
+ )
210
+ )
211
+ else:
212
+ if prev_id is None:
213
+ filters.append(FleetModel.created_at < prev_created_at)
214
+ else:
215
+ filters.append(
216
+ or_(
217
+ FleetModel.created_at < prev_created_at,
218
+ and_(FleetModel.created_at == prev_created_at, FleetModel.id > prev_id),
219
+ )
220
+ )
221
+ order_by = (FleetModel.created_at.desc(), FleetModel.id)
222
+ if ascending:
223
+ order_by = (FleetModel.created_at.asc(), FleetModel.id.desc())
224
+ res = await session.execute(
225
+ select(FleetModel)
226
+ .where(*filters)
227
+ .order_by(*order_by)
228
+ .limit(limit)
229
+ .options(joinedload(FleetModel.instances.and_(InstanceModel.deleted == False)))
230
+ )
231
+ fleet_models = list(res.unique().scalars().all())
232
+ return fleet_models
233
+
234
+
235
+ async def list_project_fleets(
236
+ session: AsyncSession,
237
+ project: ProjectModel,
238
+ names: Optional[List[str]] = None,
239
+ ) -> List[Fleet]:
240
+ fleet_models = await list_project_fleet_models(session=session, project=project, names=names)
241
+ return [fleet_model_to_fleet(v) for v in fleet_models]
242
+
243
+
244
+ async def list_project_fleet_models(
245
+ session: AsyncSession,
246
+ project: ProjectModel,
247
+ names: Optional[List[str]] = None,
248
+ include_deleted: bool = False,
249
+ ) -> List[FleetModel]:
250
+ filters = [
251
+ FleetModel.project_id == project.id,
252
+ ]
253
+ if names is not None:
254
+ filters.append(FleetModel.name.in_(names))
255
+ if not include_deleted:
256
+ filters.append(FleetModel.deleted == False)
257
+ res = await session.execute(
258
+ select(FleetModel)
259
+ .where(*filters)
260
+ .options(joinedload(FleetModel.instances.and_(InstanceModel.deleted == False)))
261
+ )
262
+ return list(res.unique().scalars().all())
263
+
264
+
265
+ async def get_fleet(
266
+ session: AsyncSession,
267
+ project: ProjectModel,
268
+ name: Optional[str] = None,
269
+ fleet_id: Optional[uuid.UUID] = None,
270
+ include_sensitive: bool = False,
271
+ ) -> Optional[Fleet]:
272
+ if fleet_id is not None:
273
+ fleet_model = await get_project_fleet_model_by_id(
274
+ session=session, project=project, fleet_id=fleet_id
275
+ )
276
+ elif name is not None:
277
+ fleet_model = await get_project_fleet_model_by_name(
278
+ session=session, project=project, name=name
279
+ )
280
+ else:
281
+ raise ServerClientError("name or id must be specified")
282
+ if fleet_model is None:
283
+ return None
284
+ return fleet_model_to_fleet(fleet_model, include_sensitive=include_sensitive)
285
+
286
+
287
+ async def get_project_fleet_model_by_id(
288
+ session: AsyncSession,
289
+ project: ProjectModel,
290
+ fleet_id: uuid.UUID,
291
+ ) -> Optional[FleetModel]:
292
+ filters = [
293
+ FleetModel.id == fleet_id,
294
+ FleetModel.project_id == project.id,
295
+ ]
296
+ res = await session.execute(
297
+ select(FleetModel)
298
+ .where(*filters)
299
+ .options(joinedload(FleetModel.instances.and_(InstanceModel.deleted == False)))
300
+ )
301
+ return res.unique().scalar_one_or_none()
302
+
303
+
304
+ async def get_project_fleet_model_by_name(
305
+ session: AsyncSession,
306
+ project: ProjectModel,
307
+ name: str,
308
+ include_deleted: bool = False,
309
+ ) -> Optional[FleetModel]:
310
+ filters = [
311
+ FleetModel.name == name,
312
+ FleetModel.project_id == project.id,
313
+ ]
314
+ if not include_deleted:
315
+ filters.append(FleetModel.deleted == False)
316
+ res = await session.execute(
317
+ select(FleetModel)
318
+ .where(*filters)
319
+ .options(joinedload(FleetModel.instances.and_(InstanceModel.deleted == False)))
320
+ )
321
+ return res.unique().scalar_one_or_none()
322
+
323
+
324
+ async def get_plan(
325
+ session: AsyncSession,
326
+ project: ProjectModel,
327
+ user: UserModel,
328
+ spec: FleetSpec,
329
+ ) -> FleetPlan:
330
+ # Spec must be copied by parsing to calculate merged_profile
331
+ effective_spec = copy_model(spec)
332
+ effective_spec = await apply_plugin_policies(
333
+ user=user.name,
334
+ project=project.name,
335
+ spec=effective_spec,
336
+ )
337
+ # Spec must be copied by parsing to calculate merged_profile
338
+ effective_spec = copy_model(effective_spec)
339
+ _validate_fleet_spec_and_set_defaults(effective_spec)
340
+
341
+ action = ApplyAction.CREATE
342
+ current_fleet: Optional[Fleet] = None
343
+ current_fleet_id: Optional[uuid.UUID] = None
344
+
345
+ if effective_spec.configuration.name is not None:
346
+ current_fleet = await get_fleet(
347
+ session=session,
348
+ project=project,
349
+ name=effective_spec.configuration.name,
350
+ include_sensitive=True,
351
+ )
352
+ if current_fleet is not None:
353
+ _set_fleet_spec_defaults(current_fleet.spec)
354
+ if _can_update_fleet_spec(current_fleet.spec, effective_spec):
355
+ action = ApplyAction.UPDATE
356
+ current_fleet_id = current_fleet.id
357
+ await _check_ssh_hosts_not_yet_added(session, effective_spec, current_fleet_id)
358
+
359
+ offers = []
360
+ if effective_spec.configuration.ssh_config is None:
361
+ offers_with_backends = await get_create_instance_offers(
362
+ project=project,
363
+ profile=effective_spec.merged_profile,
364
+ requirements=get_fleet_requirements(effective_spec),
365
+ fleet_spec=effective_spec,
366
+ blocks=effective_spec.configuration.blocks,
367
+ )
368
+ offers = [offer for _, offer in offers_with_backends]
369
+
370
+ _remove_fleet_spec_sensitive_info(effective_spec)
371
+ if current_fleet is not None:
372
+ _remove_fleet_spec_sensitive_info(current_fleet.spec)
373
+ plan = FleetPlan(
374
+ project_name=project.name,
375
+ user=user.name,
376
+ spec=spec,
377
+ effective_spec=effective_spec,
378
+ current_resource=current_fleet,
379
+ offers=offers[:50],
380
+ total_offers=len(offers),
381
+ max_offer_price=max((offer.price for offer in offers), default=None),
382
+ action=action,
383
+ )
384
+ return plan
385
+
386
+
387
+ async def get_create_instance_offers(
388
+ project: ProjectModel,
389
+ profile: Profile,
390
+ requirements: Requirements,
391
+ placement_group: Optional[PlacementGroup] = None,
392
+ fleet_spec: Optional[FleetSpec] = None,
393
+ fleet_model: Optional[FleetModel] = None,
394
+ blocks: Union[int, Literal["auto"]] = 1,
395
+ exclude_not_available: bool = False,
396
+ ) -> List[Tuple[Backend, InstanceOfferWithAvailability]]:
397
+ multinode = False
398
+ master_job_provisioning_data = None
399
+ if fleet_spec is not None:
400
+ multinode = fleet_spec.configuration.placement == InstanceGroupPlacement.CLUSTER
401
+ if fleet_model is not None:
402
+ fleet = fleet_model_to_fleet(fleet_model)
403
+ multinode = fleet.spec.configuration.placement == InstanceGroupPlacement.CLUSTER
404
+ for instance in fleet_model.instances:
405
+ jpd = instances_services.get_instance_provisioning_data(instance)
406
+ if jpd is not None:
407
+ master_job_provisioning_data = jpd
408
+ break
409
+
410
+ offers = await offers_services.get_offers_by_requirements(
411
+ project=project,
412
+ profile=profile,
413
+ requirements=requirements,
414
+ exclude_not_available=exclude_not_available,
415
+ multinode=multinode,
416
+ master_job_provisioning_data=master_job_provisioning_data,
417
+ placement_group=placement_group,
418
+ blocks=blocks,
419
+ )
420
+ offers = [
421
+ (backend, offer)
422
+ for backend, offer in offers
423
+ if offer.backend in BACKENDS_WITH_CREATE_INSTANCE_SUPPORT
424
+ ]
425
+ return offers
426
+
427
+
428
+ async def apply_plan(
429
+ session: AsyncSession,
430
+ user: UserModel,
431
+ project: ProjectModel,
432
+ plan: ApplyFleetPlanInput,
433
+ force: bool,
434
+ ) -> Fleet:
435
+ spec = await apply_plugin_policies(
436
+ user=user.name,
437
+ project=project.name,
438
+ spec=plan.spec,
439
+ )
440
+ # Spec must be copied by parsing to calculate merged_profile
441
+ spec = copy_model(spec)
442
+ _validate_fleet_spec_and_set_defaults(spec)
443
+
444
+ if spec.configuration.ssh_config is not None:
445
+ _check_can_manage_ssh_fleets(user=user, project=project)
446
+
447
+ configuration = spec.configuration
448
+ if configuration.name is None:
449
+ return await _create_fleet(
450
+ session=session,
451
+ project=project,
452
+ user=user,
453
+ spec=spec,
454
+ )
455
+
456
+ fleet_model = await get_project_fleet_model_by_name(
457
+ session=session,
458
+ project=project,
459
+ name=configuration.name,
460
+ )
461
+ if fleet_model is None:
462
+ return await _create_fleet(
463
+ session=session,
464
+ project=project,
465
+ user=user,
466
+ spec=spec,
467
+ )
468
+
469
+ instances_ids = sorted(i.id for i in fleet_model.instances if not i.deleted)
470
+ await session.commit()
471
+ async with (
472
+ get_locker(get_db().dialect_name).lock_ctx(FleetModel.__tablename__, [fleet_model.id]),
473
+ get_locker(get_db().dialect_name).lock_ctx(InstanceModel.__tablename__, instances_ids),
474
+ ):
475
+ # Refetch after lock
476
+ # TODO: Lock instances with FOR UPDATE?
477
+ res = await session.execute(
478
+ select(FleetModel)
479
+ .where(
480
+ FleetModel.project_id == project.id,
481
+ FleetModel.id == fleet_model.id,
482
+ FleetModel.deleted == False,
483
+ )
484
+ .options(
485
+ selectinload(FleetModel.instances)
486
+ .joinedload(InstanceModel.jobs)
487
+ .load_only(JobModel.id)
488
+ )
489
+ .options(selectinload(FleetModel.runs))
490
+ .execution_options(populate_existing=True)
491
+ .order_by(FleetModel.id) # take locks in order
492
+ .with_for_update(key_share=True)
493
+ )
494
+ fleet_model = res.scalars().unique().one_or_none()
495
+ if fleet_model is not None:
496
+ return await _update_fleet(
497
+ session=session,
498
+ user=user,
499
+ project=project,
500
+ spec=spec,
501
+ current_resource=plan.current_resource,
502
+ force=force,
503
+ fleet_model=fleet_model,
504
+ )
505
+
506
+ return await _create_fleet(
507
+ session=session,
508
+ project=project,
509
+ user=user,
510
+ spec=spec,
511
+ )
512
+
513
+
514
+ async def create_fleet(
515
+ session: AsyncSession,
516
+ project: ProjectModel,
517
+ user: UserModel,
518
+ spec: FleetSpec,
519
+ ) -> Fleet:
520
+ spec = await apply_plugin_policies(
521
+ user=user.name,
522
+ project=project.name,
523
+ spec=spec,
524
+ )
525
+ # Spec must be copied by parsing to calculate merged_profile
526
+ spec = copy_model(spec)
527
+ _validate_fleet_spec_and_set_defaults(spec)
528
+
529
+ if spec.configuration.ssh_config is not None:
530
+ _check_can_manage_ssh_fleets(user=user, project=project)
531
+
532
+ return await _create_fleet(session=session, project=project, user=user, spec=spec)
533
+
534
+
535
+ def create_fleet_instance_model(
536
+ session: AsyncSession,
537
+ project: ProjectModel,
538
+ username: str,
539
+ spec: FleetSpec,
540
+ instance_num: int,
541
+ ) -> InstanceModel:
542
+ profile = spec.merged_profile
543
+ requirements = get_fleet_requirements(spec)
544
+ instance_model = instances_services.create_instance_model(
545
+ session=session,
546
+ project=project,
547
+ username=username,
548
+ profile=profile,
549
+ requirements=requirements,
550
+ instance_name=f"{spec.configuration.name}-{instance_num}",
551
+ instance_num=instance_num,
552
+ reservation=spec.merged_profile.reservation,
553
+ blocks=spec.configuration.blocks,
554
+ tags=spec.configuration.tags,
555
+ )
556
+ return instance_model
557
+
558
+
559
+ async def create_fleet_ssh_instance_model(
560
+ project: ProjectModel,
561
+ spec: FleetSpec,
562
+ ssh_params: SSHParams,
563
+ env: Env,
564
+ instance_num: int,
565
+ host: Union[SSHHostParams, str],
566
+ ) -> InstanceModel:
567
+ if isinstance(host, str):
568
+ hostname = host
569
+ ssh_user = ssh_params.user
570
+ ssh_key = ssh_params.ssh_key
571
+ port = ssh_params.port
572
+ proxy_jump = ssh_params.proxy_jump
573
+ internal_ip = None
574
+ blocks = 1
575
+ else:
576
+ hostname = host.hostname
577
+ ssh_user = host.user or ssh_params.user
578
+ ssh_key = host.ssh_key or ssh_params.ssh_key
579
+ port = host.port or ssh_params.port
580
+ proxy_jump = host.proxy_jump or ssh_params.proxy_jump
581
+ internal_ip = host.internal_ip
582
+ blocks = host.blocks
583
+
584
+ if ssh_user is None or ssh_key is None:
585
+ # This should not be reachable but checked by fleet spec validation
586
+ raise ServerClientError("ssh key or user not specified")
587
+
588
+ if proxy_jump is not None:
589
+ assert proxy_jump.ssh_key is not None
590
+ ssh_proxy = SSHConnectionParams(
591
+ hostname=proxy_jump.hostname,
592
+ port=proxy_jump.port or 22,
593
+ username=proxy_jump.user,
594
+ )
595
+ ssh_proxy_keys = [proxy_jump.ssh_key]
596
+ else:
597
+ ssh_proxy = None
598
+ ssh_proxy_keys = None
599
+
600
+ instance_model = await instances_services.create_ssh_instance_model(
601
+ project=project,
602
+ instance_name=f"{spec.configuration.name}-{instance_num}",
603
+ instance_num=instance_num,
604
+ region="remote",
605
+ host=hostname,
606
+ ssh_user=ssh_user,
607
+ ssh_keys=[ssh_key],
608
+ ssh_proxy=ssh_proxy,
609
+ ssh_proxy_keys=ssh_proxy_keys,
610
+ env=env,
611
+ internal_ip=internal_ip,
612
+ instance_network=ssh_params.network,
613
+ port=port or 22,
614
+ blocks=blocks,
615
+ )
616
+ return instance_model
617
+
618
+
619
+ async def delete_fleets(
620
+ session: AsyncSession,
621
+ project: ProjectModel,
622
+ user: UserModel,
623
+ names: List[str],
624
+ instance_nums: Optional[List[int]] = None,
625
+ ):
626
+ res = await session.execute(
627
+ select(FleetModel.id)
628
+ .where(
629
+ FleetModel.project_id == project.id,
630
+ FleetModel.name.in_(names),
631
+ FleetModel.deleted == False,
632
+ )
633
+ .order_by(FleetModel.id) # take locks in order
634
+ .with_for_update(key_share=True)
635
+ )
636
+ fleets_ids = list(res.scalars().unique().all())
637
+ res = await session.execute(
638
+ select(InstanceModel.id)
639
+ .where(
640
+ InstanceModel.fleet_id.in_(fleets_ids),
641
+ InstanceModel.deleted == False,
642
+ )
643
+ .order_by(InstanceModel.id) # take locks in order
644
+ .with_for_update(key_share=True)
645
+ )
646
+ instances_ids = list(res.scalars().unique().all())
647
+ if is_db_sqlite():
648
+ # Start new transaction to see committed changes after lock
649
+ await session.commit()
650
+ async with (
651
+ get_locker(get_db().dialect_name).lock_ctx(FleetModel.__tablename__, fleets_ids),
652
+ get_locker(get_db().dialect_name).lock_ctx(InstanceModel.__tablename__, instances_ids),
653
+ ):
654
+ # Refetch after lock.
655
+ # TODO: Do not lock fleet when deleting only instances.
656
+ res = await session.execute(
657
+ select(FleetModel)
658
+ .where(FleetModel.id.in_(fleets_ids))
659
+ .options(
660
+ joinedload(FleetModel.instances.and_(InstanceModel.id.in_(instances_ids)))
661
+ .joinedload(InstanceModel.jobs)
662
+ .load_only(JobModel.id)
663
+ )
664
+ .options(
665
+ joinedload(
666
+ FleetModel.runs.and_(RunModel.status.not_in(RunStatus.finished_statuses()))
667
+ )
668
+ )
669
+ .execution_options(populate_existing=True)
670
+ )
671
+ fleet_models = res.scalars().unique().all()
672
+ fleets = [fleet_model_to_fleet(m) for m in fleet_models]
673
+ for fleet in fleets:
674
+ if fleet.spec.configuration.ssh_config is not None:
675
+ _check_can_manage_ssh_fleets(user=user, project=project)
676
+ if instance_nums is None:
677
+ logger.info("Deleting fleets: %s", [f.name for f in fleet_models])
678
+ else:
679
+ logger.info(
680
+ "Deleting fleets %s instances %s", [f.name for f in fleet_models], instance_nums
681
+ )
682
+ for fleet_model in fleet_models:
683
+ _terminate_fleet_instances(
684
+ session=session, fleet_model=fleet_model, instance_nums=instance_nums, actor=user
685
+ )
686
+ # TERMINATING fleets are deleted by process_fleets after instances are terminated
687
+ if instance_nums is None:
688
+ switch_fleet_status(
689
+ session,
690
+ fleet_model,
691
+ FleetStatus.TERMINATING,
692
+ actor=events.UserActor.from_user(user),
693
+ )
694
+ await session.commit()
695
+
696
+
697
+ def fleet_model_to_fleet(
698
+ fleet_model: FleetModel,
699
+ include_deleted_instances: bool = False,
700
+ include_sensitive: bool = False,
701
+ ) -> Fleet:
702
+ instance_models = fleet_model.instances
703
+ if not include_deleted_instances:
704
+ instance_models = [i for i in instance_models if not i.deleted]
705
+ instances = [instances_services.instance_model_to_instance(i) for i in instance_models]
706
+ instances = sorted(instances, key=lambda i: i.instance_num)
707
+ spec = get_fleet_spec(fleet_model)
708
+ if not include_sensitive:
709
+ _remove_fleet_spec_sensitive_info(spec)
710
+ return Fleet(
711
+ id=fleet_model.id,
712
+ name=fleet_model.name,
713
+ project_name=fleet_model.project.name,
714
+ spec=spec,
715
+ created_at=fleet_model.created_at,
716
+ status=fleet_model.status,
717
+ status_message=fleet_model.status_message,
718
+ instances=instances,
719
+ )
720
+
721
+
722
+ def get_fleet_spec(fleet_model: FleetModel) -> FleetSpec:
723
+ return FleetSpec.__response__.parse_raw(fleet_model.spec)
724
+
725
+
726
+ async def generate_fleet_name(session: AsyncSession, project: ProjectModel) -> str:
727
+ res = await session.execute(
728
+ select(FleetModel.name).where(
729
+ FleetModel.project_id == project.id,
730
+ FleetModel.deleted == False,
731
+ )
732
+ )
733
+ names = set(res.scalars().all())
734
+ while True:
735
+ name = random_names.generate_name()
736
+ if name not in names:
737
+ return name
738
+
739
+
740
+ def is_fleet_in_use(fleet_model: FleetModel, instance_nums: Optional[List[int]] = None) -> bool:
741
+ instances_in_use = [i for i in fleet_model.instances if i.jobs and not i.deleted]
742
+ selected_instance_in_use = instances_in_use
743
+ if instance_nums is not None:
744
+ selected_instance_in_use = [i for i in instances_in_use if i.instance_num in instance_nums]
745
+ active_runs = [r for r in fleet_model.runs if not r.status.is_finished()]
746
+ return len(selected_instance_in_use) > 0 or len(instances_in_use) == 0 and len(active_runs) > 0
747
+
748
+
749
+ def is_fleet_empty(fleet_model: FleetModel) -> bool:
750
+ active_instances = [i for i in fleet_model.instances if not i.deleted]
751
+ return len(active_instances) == 0
752
+
753
+
754
+ def is_cloud_cluster(fleet_model: FleetModel) -> bool:
755
+ fleet = fleet_model_to_fleet(fleet_model)
756
+ return (
757
+ fleet.spec.configuration.placement == InstanceGroupPlacement.CLUSTER
758
+ and fleet.spec.configuration.ssh_config is None
759
+ )
760
+
761
+
762
+ def get_fleet_requirements(fleet_spec: FleetSpec) -> Requirements:
763
+ profile = fleet_spec.merged_profile
764
+ requirements = Requirements(
765
+ resources=fleet_spec.configuration.resources or ResourcesSpec(),
766
+ max_price=profile.max_price,
767
+ spot=get_policy_map(profile.spot_policy, default=SpotPolicy.ONDEMAND),
768
+ reservation=fleet_spec.configuration.reservation,
769
+ multinode=fleet_spec.configuration.placement == InstanceGroupPlacement.CLUSTER,
770
+ )
771
+ return requirements
772
+
773
+
774
+ def get_next_instance_num(taken_instance_nums: set[int]) -> int:
775
+ if not taken_instance_nums:
776
+ return 0
777
+ min_instance_num = min(taken_instance_nums)
778
+ if min_instance_num > 0:
779
+ return 0
780
+ instance_num = min_instance_num + 1
781
+ while True:
782
+ if instance_num not in taken_instance_nums:
783
+ return instance_num
784
+ instance_num += 1
785
+
786
+
787
+ def get_fleet_master_instance_provisioning_data(
788
+ fleet_model: FleetModel,
789
+ fleet_spec: FleetSpec,
790
+ ) -> Optional[JobProvisioningData]:
791
+ master_instance_provisioning_data = None
792
+ if fleet_spec.configuration.placement == InstanceGroupPlacement.CLUSTER:
793
+ # Offers for master jobs must be in the same cluster as existing instances.
794
+ fleet_instance_models = [im for im in fleet_model.instances if not im.deleted]
795
+ if len(fleet_instance_models) > 0:
796
+ master_instance_model = fleet_instance_models[0]
797
+ master_instance_provisioning_data = JobProvisioningData.__response__.parse_raw(
798
+ master_instance_model.job_provisioning_data
799
+ )
800
+ return master_instance_provisioning_data
801
+
802
+
803
+ def can_create_new_cloud_instance_in_fleet(fleet: Fleet) -> bool:
804
+ if fleet.spec.configuration.ssh_config is not None:
805
+ return False
806
+ active_instances = [i for i in fleet.instances if i.status.is_active()]
807
+ # nodes.max is a soft limit that can be exceeded when provisioning concurrently.
808
+ # The fleet consolidation logic will remove redundant nodes eventually.
809
+ if (
810
+ fleet.spec.configuration.nodes is not None
811
+ and fleet.spec.configuration.nodes.max is not None
812
+ and len(active_instances) >= fleet.spec.configuration.nodes.max
813
+ ):
814
+ return False
815
+ return True
816
+
817
+
818
+ def check_can_create_new_cloud_instance_in_fleet(fleet: Fleet):
819
+ if not can_create_new_cloud_instance_in_fleet(fleet):
820
+ raise ValueError("Cannot fit new cloud instance into fleet")
821
+
822
+
823
+ async def _create_fleet(
824
+ session: AsyncSession,
825
+ project: ProjectModel,
826
+ user: UserModel,
827
+ spec: FleetSpec,
828
+ ) -> Fleet:
829
+ lock_namespace = f"fleet_names_{project.name}"
830
+ if is_db_sqlite():
831
+ # Start new transaction to see committed changes after lock
832
+ await session.commit()
833
+ elif is_db_postgres():
834
+ await session.execute(
835
+ select(func.pg_advisory_xact_lock(string_to_lock_id(lock_namespace)))
836
+ )
837
+ lock, _ = get_locker(get_db().dialect_name).get_lockset(lock_namespace)
838
+ async with lock:
839
+ if spec.configuration.name is not None:
840
+ fleet_model = await get_project_fleet_model_by_name(
841
+ session=session,
842
+ project=project,
843
+ name=spec.configuration.name,
844
+ )
845
+ if fleet_model is not None:
846
+ raise ResourceExistsError()
847
+ else:
848
+ spec.configuration.name = await generate_fleet_name(session=session, project=project)
849
+
850
+ fleet_model = FleetModel(
851
+ id=uuid.uuid4(),
852
+ name=spec.configuration.name,
853
+ project=project,
854
+ status=FleetStatus.ACTIVE,
855
+ spec=spec.json(),
856
+ instances=[],
857
+ )
858
+ session.add(fleet_model)
859
+ events.emit(
860
+ session,
861
+ f"Fleet created. Status: {fleet_model.status.upper()}",
862
+ actor=events.UserActor.from_user(user),
863
+ targets=[events.Target.from_model(fleet_model)],
864
+ )
865
+ if spec.configuration.ssh_config is not None:
866
+ for i, host in enumerate(spec.configuration.ssh_config.hosts):
867
+ instance_model = await create_fleet_ssh_instance_model(
868
+ project=project,
869
+ spec=spec,
870
+ ssh_params=spec.configuration.ssh_config,
871
+ env=spec.configuration.env,
872
+ instance_num=i,
873
+ host=host,
874
+ )
875
+ events.emit(
876
+ session,
877
+ (
878
+ "Instance created on fleet submission."
879
+ f" Status: {instance_model.status.upper()}"
880
+ ),
881
+ actor=events.UserActor.from_user(user),
882
+ targets=[events.Target.from_model(instance_model)],
883
+ )
884
+ fleet_model.instances.append(instance_model)
885
+ else:
886
+ for i in range(_get_fleet_nodes_to_provision(spec)):
887
+ instance_model = create_fleet_instance_model(
888
+ session=session,
889
+ project=project,
890
+ username=user.name,
891
+ spec=spec,
892
+ instance_num=i,
893
+ )
894
+ events.emit(
895
+ session,
896
+ (
897
+ "Instance created on fleet submission."
898
+ f" Status: {instance_model.status.upper()}"
899
+ ),
900
+ # Set `SystemActor` for consistency with other places where cloud instances can be
901
+ # created (fleet spec consolidation, job provisioning, etc). Think of the fleet as being
902
+ # created by the user, while the cloud instance is created by the system to satisfy the
903
+ # fleet spec.
904
+ actor=events.SystemActor(),
905
+ targets=[events.Target.from_model(instance_model)],
906
+ )
907
+ fleet_model.instances.append(instance_model)
908
+ await session.commit()
909
+ return fleet_model_to_fleet(fleet_model)
910
+
911
+
912
+ async def _update_fleet(
913
+ session: AsyncSession,
914
+ user: UserModel,
915
+ project: ProjectModel,
916
+ spec: FleetSpec,
917
+ current_resource: Optional[Fleet],
918
+ force: bool,
919
+ fleet_model: FleetModel,
920
+ ) -> Fleet:
921
+ fleet = fleet_model_to_fleet(fleet_model)
922
+ _set_fleet_spec_defaults(fleet.spec)
923
+ fleet_sensitive = fleet_model_to_fleet(fleet_model, include_sensitive=True)
924
+ _set_fleet_spec_defaults(fleet_sensitive.spec)
925
+
926
+ if not force:
927
+ if current_resource is not None:
928
+ _set_fleet_spec_defaults(current_resource.spec)
929
+ if (
930
+ current_resource is None
931
+ or current_resource.id != fleet.id
932
+ or current_resource.spec != fleet.spec
933
+ ):
934
+ raise ServerClientError(
935
+ "Failed to apply plan. Resource has been changed. Try again or use force apply."
936
+ )
937
+
938
+ _check_can_update_fleet_spec(fleet_sensitive.spec, spec)
939
+
940
+ spec_json = spec.json()
941
+ fleet_model.spec = spec_json
942
+
943
+ if (
944
+ fleet_sensitive.spec.configuration.ssh_config is not None
945
+ and spec.configuration.ssh_config is not None
946
+ ):
947
+ added_hosts, removed_hosts, changed_hosts = _calculate_ssh_hosts_changes(
948
+ current=fleet_sensitive.spec.configuration.ssh_config.hosts,
949
+ new=spec.configuration.ssh_config.hosts,
950
+ )
951
+ # `_check_can_update_fleet_spec` ensures hosts are not changed
952
+ assert not changed_hosts, changed_hosts
953
+ active_instance_nums: set[int] = set()
954
+ removed_instance_nums: list[int] = []
955
+ if removed_hosts or added_hosts:
956
+ for instance_model in fleet_model.instances:
957
+ if instance_model.deleted:
958
+ continue
959
+ active_instance_nums.add(instance_model.instance_num)
960
+ rci = get_instance_remote_connection_info(instance_model)
961
+ if rci is None:
962
+ logger.error(
963
+ "Cloud instance %s in SSH fleet %s",
964
+ instance_model.id,
965
+ fleet_model.id,
966
+ )
967
+ continue
968
+ if rci.host in removed_hosts:
969
+ removed_instance_nums.append(instance_model.instance_num)
970
+ if added_hosts:
971
+ await _check_ssh_hosts_not_yet_added(session, spec, fleet.id)
972
+ for host in added_hosts.values():
973
+ instance_num = get_next_instance_num(active_instance_nums)
974
+ instance_model = await create_fleet_ssh_instance_model(
975
+ project=project,
976
+ spec=spec,
977
+ ssh_params=spec.configuration.ssh_config,
978
+ env=spec.configuration.env,
979
+ instance_num=instance_num,
980
+ host=host,
981
+ )
982
+ events.emit(
983
+ session,
984
+ f"Instance created on fleet update. Status: {instance_model.status.upper()}",
985
+ actor=events.UserActor.from_user(user),
986
+ targets=[events.Target.from_model(instance_model)],
987
+ )
988
+ fleet_model.instances.append(instance_model)
989
+ active_instance_nums.add(instance_num)
990
+ if removed_instance_nums:
991
+ _terminate_fleet_instances(session, fleet_model, removed_instance_nums, actor=user)
992
+
993
+ await session.commit()
994
+ return fleet_model_to_fleet(fleet_model)
995
+
996
+
997
+ def _can_update_fleet_spec(current_fleet_spec: FleetSpec, new_fleet_spec: FleetSpec) -> bool:
998
+ try:
999
+ _check_can_update_fleet_spec(current_fleet_spec, new_fleet_spec)
1000
+ except ServerClientError as e:
1001
+ logger.debug("Run cannot be updated: %s", repr(e))
1002
+ return False
1003
+ return True
1004
+
1005
+
1006
+ M = TypeVar("M", bound=CoreModel)
1007
+
1008
+
1009
+ def _check_can_update(*updatable_fields: str):
1010
+ def decorator(fn: Callable[[M, M, ModelDiff], None]) -> Callable[[M, M], None]:
1011
+ @wraps(fn)
1012
+ def inner(current: M, new: M):
1013
+ diff = _check_can_update_inner(current, new, updatable_fields)
1014
+ fn(current, new, diff)
1015
+
1016
+ return inner
1017
+
1018
+ return decorator
1019
+
1020
+
1021
+ def _check_can_update_inner(current: M, new: M, updatable_fields: tuple[str, ...]) -> ModelDiff:
1022
+ diff = diff_models(current, new)
1023
+ changed_fields = diff.keys()
1024
+ if not (changed_fields <= set(updatable_fields)):
1025
+ raise ServerClientError(
1026
+ f"Failed to update fields {list(changed_fields)}."
1027
+ f" Can only update {list(updatable_fields)}."
1028
+ )
1029
+ return diff
1030
+
1031
+
1032
+ @_check_can_update("configuration", "configuration_path")
1033
+ def _check_can_update_fleet_spec(current: FleetSpec, new: FleetSpec, diff: ModelDiff):
1034
+ if "configuration" in diff:
1035
+ _check_can_update_fleet_configuration(current.configuration, new.configuration)
1036
+
1037
+
1038
+ @_check_can_update("ssh_config")
1039
+ def _check_can_update_fleet_configuration(
1040
+ current: FleetConfiguration, new: FleetConfiguration, diff: ModelDiff
1041
+ ):
1042
+ if "ssh_config" in diff:
1043
+ current_ssh_config = current.ssh_config
1044
+ new_ssh_config = new.ssh_config
1045
+ if current_ssh_config is None:
1046
+ if new_ssh_config is not None:
1047
+ raise ServerClientError("Fleet type changed from Cloud to SSH, cannot update")
1048
+ elif new_ssh_config is None:
1049
+ raise ServerClientError("Fleet type changed from SSH to Cloud, cannot update")
1050
+ else:
1051
+ _check_can_update_ssh_config(current_ssh_config, new_ssh_config)
1052
+
1053
+
1054
+ @_check_can_update("hosts")
1055
+ def _check_can_update_ssh_config(current: SSHParams, new: SSHParams, diff: ModelDiff):
1056
+ if "hosts" in diff:
1057
+ _, _, changed_hosts = _calculate_ssh_hosts_changes(current.hosts, new.hosts)
1058
+ if changed_hosts:
1059
+ raise ServerClientError(
1060
+ f"Hosts configuration changed, cannot update: {list(changed_hosts)}"
1061
+ )
1062
+
1063
+
1064
+ def _calculate_ssh_hosts_changes(
1065
+ current: list[Union[SSHHostParams, str]], new: list[Union[SSHHostParams, str]]
1066
+ ) -> tuple[dict[str, Union[SSHHostParams, str]], set[str], set[str]]:
1067
+ current_hosts = {h if isinstance(h, str) else h.hostname: h for h in current}
1068
+ new_hosts = {h if isinstance(h, str) else h.hostname: h for h in new}
1069
+ added_hosts = {h: new_hosts[h] for h in new_hosts.keys() - current_hosts}
1070
+ removed_hosts = current_hosts.keys() - new_hosts
1071
+ changed_hosts: set[str] = set()
1072
+ for host in current_hosts.keys() & new_hosts:
1073
+ current_host = current_hosts[host]
1074
+ new_host = new_hosts[host]
1075
+ if isinstance(current_host, str) or isinstance(new_host, str):
1076
+ if current_host != new_host:
1077
+ changed_hosts.add(host)
1078
+ elif diff_models(
1079
+ current_host, new_host, reset={"identity_file": True, "proxy_jump": {"identity_file"}}
1080
+ ):
1081
+ changed_hosts.add(host)
1082
+ return added_hosts, removed_hosts, changed_hosts
1083
+
1084
+
1085
+ def _check_can_manage_ssh_fleets(user: UserModel, project: ProjectModel):
1086
+ if user.global_role == GlobalRole.ADMIN:
1087
+ return
1088
+ member = get_member(user=user, project=project)
1089
+ if member is None:
1090
+ raise ForbiddenError()
1091
+ permissions = get_member_permissions(member)
1092
+ if permissions.can_manage_ssh_fleets:
1093
+ return
1094
+ raise ForbiddenError()
1095
+
1096
+
1097
+ async def _check_ssh_hosts_not_yet_added(
1098
+ session: AsyncSession, spec: FleetSpec, current_fleet_id: Optional[uuid.UUID] = None
1099
+ ):
1100
+ if spec.configuration.ssh_config and spec.configuration.ssh_config.hosts:
1101
+ # there are manually listed hosts, need to check them for existence
1102
+ active_instances = await list_active_remote_instances(session=session)
1103
+
1104
+ existing_hosts = set()
1105
+ for instance in active_instances:
1106
+ # ignore instances belonging to the same fleet -- in-place update/recreate
1107
+ if current_fleet_id is not None and instance.fleet_id == current_fleet_id:
1108
+ continue
1109
+ instance_conn_info = RemoteConnectionInfo.parse_raw(
1110
+ cast(str, instance.remote_connection_info)
1111
+ )
1112
+ existing_hosts.add(instance_conn_info.host)
1113
+
1114
+ instances_already_in_fleet = []
1115
+ for new_instance in spec.configuration.ssh_config.hosts:
1116
+ hostname = new_instance if isinstance(new_instance, str) else new_instance.hostname
1117
+ if hostname in existing_hosts:
1118
+ instances_already_in_fleet.append(hostname)
1119
+
1120
+ if instances_already_in_fleet:
1121
+ raise ServerClientError(
1122
+ msg=f"Instances [{', '.join(instances_already_in_fleet)}] are already assigned to a fleet."
1123
+ )
1124
+
1125
+
1126
+ def _remove_fleet_spec_sensitive_info(spec: FleetSpec):
1127
+ if spec.configuration.ssh_config is not None:
1128
+ spec.configuration.ssh_config.ssh_key = None
1129
+ for host in spec.configuration.ssh_config.hosts:
1130
+ if not isinstance(host, str):
1131
+ host.ssh_key = None
1132
+
1133
+
1134
+ def _validate_fleet_spec_and_set_defaults(spec: FleetSpec):
1135
+ if spec.configuration.name is not None:
1136
+ validate_dstack_resource_name(spec.configuration.name)
1137
+ if spec.configuration.ssh_config is None and spec.configuration.nodes is None:
1138
+ raise ServerClientError("No ssh_config or nodes specified")
1139
+ if spec.configuration.ssh_config is not None and spec.configuration.nodes is not None:
1140
+ raise ServerClientError("ssh_config and nodes are mutually exclusive")
1141
+ if spec.configuration.ssh_config is not None:
1142
+ _validate_all_ssh_params_specified(spec.configuration.ssh_config)
1143
+ if spec.configuration.ssh_config.ssh_key is not None:
1144
+ _validate_ssh_key(spec.configuration.ssh_config.ssh_key)
1145
+ for host in spec.configuration.ssh_config.hosts:
1146
+ if isinstance(host, SSHHostParams) and host.ssh_key is not None:
1147
+ _validate_ssh_key(host.ssh_key)
1148
+ _validate_internal_ips(spec.configuration.ssh_config)
1149
+ _set_fleet_spec_defaults(spec)
1150
+
1151
+
1152
+ def _set_fleet_spec_defaults(spec: FleetSpec):
1153
+ if spec.configuration.resources is not None:
1154
+ set_resources_defaults(spec.configuration.resources)
1155
+
1156
+
1157
+ def _validate_all_ssh_params_specified(ssh_config: SSHParams):
1158
+ for host in ssh_config.hosts:
1159
+ if isinstance(host, str):
1160
+ if ssh_config.ssh_key is None:
1161
+ raise ServerClientError(f"No ssh key specified for host {host}")
1162
+ if ssh_config.user is None:
1163
+ raise ServerClientError(f"No ssh user specified for host {host}")
1164
+ else:
1165
+ if ssh_config.ssh_key is None and host.ssh_key is None:
1166
+ raise ServerClientError(f"No ssh key specified for host {host.hostname}")
1167
+ if ssh_config.user is None and host.user is None:
1168
+ raise ServerClientError(f"No ssh user specified for host {host.hostname}")
1169
+
1170
+
1171
+ def _validate_ssh_key(ssh_key: SSHKey):
1172
+ if ssh_key.private is None:
1173
+ raise ServerClientError("Private key not provided")
1174
+ try:
1175
+ pkey_from_str(ssh_key.private)
1176
+ except ValueError:
1177
+ raise ServerClientError(
1178
+ "Unsupported key type. "
1179
+ "The key type should be RSA, ECDSA, or Ed25519 and should not be encrypted with passphrase."
1180
+ )
1181
+
1182
+
1183
+ def _validate_internal_ips(ssh_config: SSHParams):
1184
+ internal_ips_num = 0
1185
+ for host in ssh_config.hosts:
1186
+ if not isinstance(host, str) and host.internal_ip is not None:
1187
+ internal_ips_num += 1
1188
+ if internal_ips_num != 0 and internal_ips_num != len(ssh_config.hosts):
1189
+ raise ServerClientError("internal_ip must be specified for all hosts")
1190
+ if internal_ips_num > 0 and ssh_config.network is not None:
1191
+ raise ServerClientError("internal_ip is mutually exclusive with network")
1192
+
1193
+
1194
+ def _get_fleet_nodes_to_provision(spec: FleetSpec) -> int:
1195
+ if spec.configuration.nodes is None:
1196
+ return 0
1197
+ return spec.configuration.nodes.target
1198
+
1199
+
1200
+ def _terminate_fleet_instances(
1201
+ session: AsyncSession,
1202
+ fleet_model: FleetModel,
1203
+ instance_nums: Optional[List[int]],
1204
+ actor: UserModel,
1205
+ ):
1206
+ if is_fleet_in_use(fleet_model, instance_nums=instance_nums):
1207
+ if instance_nums is not None:
1208
+ raise ServerClientError(
1209
+ f"Failed to delete fleet {fleet_model.name} instances {instance_nums}. Fleet instances are in use."
1210
+ )
1211
+ raise ServerClientError(f"Failed to delete fleet {fleet_model.name}. Fleet is in use.")
1212
+ for instance in fleet_model.instances:
1213
+ if instance_nums is not None and instance.instance_num not in instance_nums:
1214
+ continue
1215
+ if instance.status == InstanceStatus.TERMINATED:
1216
+ instance.deleted = True
1217
+ else:
1218
+ instance.termination_reason = InstanceTerminationReason.TERMINATED_BY_USER
1219
+ switch_instance_status(
1220
+ session,
1221
+ instance,
1222
+ InstanceStatus.TERMINATING,
1223
+ actor=events.UserActor.from_user(actor),
1224
+ )