dstack 0.18.44__py3-none-any.whl → 0.19.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. dstack/_internal/cli/commands/gateway.py +15 -3
  2. dstack/_internal/cli/commands/logs.py +0 -22
  3. dstack/_internal/cli/commands/stats.py +8 -17
  4. dstack/_internal/cli/main.py +1 -5
  5. dstack/_internal/cli/services/configurators/fleet.py +4 -39
  6. dstack/_internal/cli/services/configurators/run.py +22 -21
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/core/backends/__init__.py +56 -39
  10. dstack/_internal/core/backends/aws/__init__.py +0 -25
  11. dstack/_internal/core/backends/aws/auth.py +1 -10
  12. dstack/_internal/core/backends/aws/backend.py +26 -0
  13. dstack/_internal/core/backends/aws/compute.py +20 -45
  14. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  15. dstack/_internal/core/backends/aws/models.py +135 -0
  16. dstack/_internal/core/backends/aws/resources.py +1 -1
  17. dstack/_internal/core/backends/azure/__init__.py +0 -20
  18. dstack/_internal/core/backends/azure/auth.py +2 -11
  19. dstack/_internal/core/backends/azure/backend.py +21 -0
  20. dstack/_internal/core/backends/azure/compute.py +13 -27
  21. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  22. dstack/_internal/core/backends/azure/models.py +89 -0
  23. dstack/_internal/core/backends/base/__init__.py +0 -12
  24. dstack/_internal/core/backends/base/backend.py +18 -0
  25. dstack/_internal/core/backends/base/compute.py +153 -33
  26. dstack/_internal/core/backends/base/configurator.py +105 -0
  27. dstack/_internal/core/backends/base/models.py +14 -0
  28. dstack/_internal/core/backends/configurators.py +138 -0
  29. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  30. dstack/_internal/core/backends/cudo/backend.py +16 -0
  31. dstack/_internal/core/backends/cudo/compute.py +8 -26
  32. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  33. dstack/_internal/core/backends/cudo/models.py +37 -0
  34. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  35. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  36. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  37. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  38. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  39. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  40. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  41. dstack/_internal/core/backends/gcp/auth.py +2 -11
  42. dstack/_internal/core/backends/gcp/backend.py +17 -0
  43. dstack/_internal/core/backends/gcp/compute.py +13 -43
  44. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  45. dstack/_internal/core/backends/gcp/models.py +125 -0
  46. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  47. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  48. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  49. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  50. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  51. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  52. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  53. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  54. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  55. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  56. dstack/_internal/core/backends/local/__init__.py +0 -13
  57. dstack/_internal/core/backends/local/backend.py +14 -0
  58. dstack/_internal/core/backends/local/compute.py +16 -2
  59. dstack/_internal/core/backends/models.py +128 -0
  60. dstack/_internal/core/backends/oci/__init__.py +0 -15
  61. dstack/_internal/core/backends/oci/auth.py +1 -5
  62. dstack/_internal/core/backends/oci/backend.py +16 -0
  63. dstack/_internal/core/backends/oci/compute.py +9 -23
  64. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  65. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  66. dstack/_internal/core/backends/oci/region.py +1 -1
  67. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  68. dstack/_internal/core/backends/runpod/backend.py +16 -0
  69. dstack/_internal/core/backends/runpod/compute.py +7 -3
  70. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  71. dstack/_internal/core/backends/runpod/models.py +54 -0
  72. dstack/_internal/core/backends/template/__init__.py +0 -0
  73. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  74. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  75. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  76. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  77. dstack/_internal/core/backends/tensordock/models.py +38 -0
  78. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  79. dstack/_internal/core/backends/vastai/backend.py +16 -0
  80. dstack/_internal/core/backends/vastai/compute.py +2 -2
  81. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  82. dstack/_internal/core/backends/vastai/models.py +37 -0
  83. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  84. dstack/_internal/core/backends/vultr/backend.py +16 -0
  85. dstack/_internal/core/backends/vultr/compute.py +10 -24
  86. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  87. dstack/_internal/core/backends/vultr/models.py +34 -0
  88. dstack/_internal/core/models/backends/__init__.py +0 -184
  89. dstack/_internal/core/models/backends/base.py +0 -19
  90. dstack/_internal/core/models/configurations.py +20 -15
  91. dstack/_internal/core/models/envs.py +4 -3
  92. dstack/_internal/core/models/fleets.py +17 -22
  93. dstack/_internal/core/models/gateways.py +3 -3
  94. dstack/_internal/core/models/instances.py +24 -0
  95. dstack/_internal/core/models/profiles.py +41 -46
  96. dstack/_internal/core/models/projects.py +1 -1
  97. dstack/_internal/core/models/repos/base.py +0 -5
  98. dstack/_internal/core/models/repos/local.py +3 -3
  99. dstack/_internal/core/models/repos/remote.py +26 -12
  100. dstack/_internal/core/models/repos/virtual.py +1 -1
  101. dstack/_internal/core/models/resources.py +45 -76
  102. dstack/_internal/core/models/runs.py +17 -19
  103. dstack/_internal/core/models/volumes.py +1 -3
  104. dstack/_internal/core/services/profiles.py +7 -16
  105. dstack/_internal/core/services/repos.py +0 -4
  106. dstack/_internal/server/app.py +0 -3
  107. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  108. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  109. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  110. dstack/_internal/server/background/tasks/process_placement_groups.py +4 -1
  111. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_running_jobs.py +14 -5
  113. dstack/_internal/server/background/tasks/process_submitted_jobs.py +16 -37
  114. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  115. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  116. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  117. dstack/_internal/server/models.py +48 -9
  118. dstack/_internal/server/routers/backends.py +14 -23
  119. dstack/_internal/server/routers/instances.py +3 -4
  120. dstack/_internal/server/routers/metrics.py +10 -8
  121. dstack/_internal/server/routers/prometheus.py +1 -1
  122. dstack/_internal/server/routers/repos.py +1 -2
  123. dstack/_internal/server/routers/runs.py +13 -59
  124. dstack/_internal/server/schemas/gateways.py +14 -23
  125. dstack/_internal/server/schemas/projects.py +7 -2
  126. dstack/_internal/server/schemas/repos.py +2 -38
  127. dstack/_internal/server/schemas/runner.py +1 -0
  128. dstack/_internal/server/schemas/runs.py +1 -24
  129. dstack/_internal/server/services/backends/__init__.py +85 -158
  130. dstack/_internal/server/services/config.py +52 -576
  131. dstack/_internal/server/services/fleets.py +8 -103
  132. dstack/_internal/server/services/gateways/__init__.py +12 -4
  133. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  134. dstack/_internal/server/services/jobs/__init__.py +9 -6
  135. dstack/_internal/server/services/jobs/configurators/base.py +16 -0
  136. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  137. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  138. dstack/_internal/server/services/metrics.py +39 -13
  139. dstack/_internal/server/services/offers.py +1 -1
  140. dstack/_internal/server/services/projects.py +23 -14
  141. dstack/_internal/server/services/prometheus.py +176 -18
  142. dstack/_internal/server/services/runs.py +24 -16
  143. dstack/_internal/server/services/volumes.py +8 -4
  144. dstack/_internal/server/statics/index.html +1 -1
  145. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js → main-4fd5a4770eff59325ee3.js} +7 -7
  146. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js.map → main-4fd5a4770eff59325ee3.js.map} +1 -1
  147. dstack/_internal/server/testing/common.py +58 -32
  148. dstack/_internal/utils/json_schema.py +6 -0
  149. dstack/_internal/utils/ssh.py +2 -1
  150. dstack/api/__init__.py +4 -0
  151. dstack/api/_public/__init__.py +16 -20
  152. dstack/api/_public/backends.py +1 -1
  153. dstack/api/_public/repos.py +36 -36
  154. dstack/api/_public/runs.py +167 -83
  155. dstack/api/server/__init__.py +11 -13
  156. dstack/api/server/_backends.py +12 -16
  157. dstack/api/server/_fleets.py +15 -57
  158. dstack/api/server/_gateways.py +3 -14
  159. dstack/api/server/_repos.py +1 -4
  160. dstack/api/server/_runs.py +21 -100
  161. dstack/api/server/_volumes.py +10 -5
  162. dstack/version.py +1 -1
  163. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/METADATA +1 -1
  164. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/RECORD +218 -204
  165. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  166. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  167. tests/_internal/core/backends/aws/test_resources.py +1 -1
  168. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  169. tests/_internal/core/backends/cudo/__init__.py +0 -0
  170. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  171. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  172. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  173. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  174. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  175. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  176. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  177. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  178. tests/_internal/core/backends/runpod/__init__.py +0 -0
  179. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  180. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  181. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  182. tests/_internal/core/backends/vastai/__init__.py +0 -0
  183. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  184. tests/_internal/core/backends/vultr/__init__.py +0 -0
  185. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  186. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  187. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  188. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  189. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  190. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +0 -3
  191. tests/_internal/server/background/tasks/test_process_running_jobs.py +0 -21
  192. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  193. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  194. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  195. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  196. tests/_internal/server/routers/test_backends.py +6 -764
  197. tests/_internal/server/routers/test_fleets.py +0 -26
  198. tests/_internal/server/routers/test_gateways.py +27 -3
  199. tests/_internal/server/routers/test_instances.py +0 -10
  200. tests/_internal/server/routers/test_metrics.py +27 -0
  201. tests/_internal/server/routers/test_projects.py +56 -0
  202. tests/_internal/server/routers/test_prometheus.py +116 -27
  203. tests/_internal/server/routers/test_repos.py +0 -15
  204. tests/_internal/server/routers/test_runs.py +4 -219
  205. tests/_internal/server/routers/test_volumes.py +2 -3
  206. tests/_internal/server/services/backends/__init__.py +0 -0
  207. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  208. tests/_internal/server/services/test_config.py +7 -4
  209. tests/_internal/server/services/test_fleets.py +1 -4
  210. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  211. tests/_internal/server/services/test_metrics.py +9 -5
  212. tests/_internal/server/services/test_repos.py +1 -14
  213. tests/_internal/server/services/test_runs.py +0 -4
  214. dstack/_internal/cli/commands/pool.py +0 -581
  215. dstack/_internal/cli/commands/run.py +0 -75
  216. dstack/_internal/core/backends/aws/config.py +0 -18
  217. dstack/_internal/core/backends/azure/config.py +0 -12
  218. dstack/_internal/core/backends/base/config.py +0 -5
  219. dstack/_internal/core/backends/cudo/config.py +0 -9
  220. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  221. dstack/_internal/core/backends/gcp/config.py +0 -22
  222. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  223. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  224. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  225. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  226. dstack/_internal/core/backends/nebius/compute.py +0 -220
  227. dstack/_internal/core/backends/nebius/config.py +0 -6
  228. dstack/_internal/core/backends/nebius/types.py +0 -37
  229. dstack/_internal/core/backends/oci/config.py +0 -6
  230. dstack/_internal/core/backends/runpod/config.py +0 -17
  231. dstack/_internal/core/backends/tensordock/config.py +0 -9
  232. dstack/_internal/core/backends/vastai/config.py +0 -6
  233. dstack/_internal/core/backends/vultr/config.py +0 -9
  234. dstack/_internal/core/models/backends/aws.py +0 -86
  235. dstack/_internal/core/models/backends/azure.py +0 -68
  236. dstack/_internal/core/models/backends/cudo.py +0 -43
  237. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  238. dstack/_internal/core/models/backends/gcp.py +0 -67
  239. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  240. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  241. dstack/_internal/core/models/backends/nebius.py +0 -54
  242. dstack/_internal/core/models/backends/runpod.py +0 -42
  243. dstack/_internal/core/models/backends/tensordock.py +0 -44
  244. dstack/_internal/core/models/backends/vastai.py +0 -43
  245. dstack/_internal/core/models/backends/vultr.py +0 -40
  246. dstack/_internal/core/models/pools.py +0 -43
  247. dstack/_internal/server/routers/pools.py +0 -142
  248. dstack/_internal/server/schemas/pools.py +0 -38
  249. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  250. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  251. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  252. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  253. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  254. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  255. dstack/_internal/server/services/backends/configurators/runpod.py +0 -67
  256. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  257. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  258. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  259. dstack/api/_public/pools.py +0 -41
  260. dstack/api/_public/resources.py +0 -105
  261. dstack/api/server/_pools.py +0 -63
  262. tests/_internal/server/routers/test_pools.py +0 -612
  263. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  264. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/LICENSE.md +0 -0
  265. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/WHEEL +0 -0
  266. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/entry_points.txt +0 -0
  267. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/top_level.txt +0 -0
@@ -27,7 +27,6 @@ from dstack._internal.server.testing.common import (
27
27
  create_fleet,
28
28
  create_instance,
29
29
  create_job,
30
- create_pool,
31
30
  create_project,
32
31
  create_repo,
33
32
  create_run,
@@ -346,8 +345,6 @@ class TestCreateFleet:
346
345
  "retry": None,
347
346
  "max_price": None,
348
347
  "idle_duration": None,
349
- "termination_policy": None,
350
- "termination_idle_time": None,
351
348
  "type": "fleet",
352
349
  "name": "test-fleet",
353
350
  "reservation": None,
@@ -360,16 +357,11 @@ class TestCreateFleet:
360
357
  "instance_types": None,
361
358
  "spot_policy": None,
362
359
  "retry": None,
363
- "retry_policy": None,
364
360
  "max_duration": None,
365
361
  "stop_duration": None,
366
362
  "max_price": None,
367
- "pool_name": None,
368
- "instance_name": None,
369
363
  "creation_policy": None,
370
364
  "idle_duration": None,
371
- "termination_policy": None,
372
- "termination_idle_time": None,
373
365
  "utilization_policy": None,
374
366
  "name": "",
375
367
  "default": False,
@@ -394,7 +386,6 @@ class TestCreateFleet:
394
386
  "unreachable": False,
395
387
  "termination_reason": None,
396
388
  "created": "2023-01-02T03:04:00+00:00",
397
- "pool_name": None,
398
389
  "backend": None,
399
390
  "region": None,
400
391
  "availability_zone": None,
@@ -472,8 +463,6 @@ class TestCreateFleet:
472
463
  "retry": None,
473
464
  "max_price": None,
474
465
  "idle_duration": None,
475
- "termination_policy": None,
476
- "termination_idle_time": None,
477
466
  "type": "fleet",
478
467
  "name": spec.configuration.name,
479
468
  "reservation": None,
@@ -486,16 +475,11 @@ class TestCreateFleet:
486
475
  "instance_types": None,
487
476
  "spot_policy": None,
488
477
  "retry": None,
489
- "retry_policy": None,
490
478
  "max_duration": None,
491
479
  "stop_duration": None,
492
480
  "max_price": None,
493
- "pool_name": None,
494
- "instance_name": None,
495
481
  "creation_policy": None,
496
482
  "idle_duration": None,
497
- "termination_policy": None,
498
- "termination_idle_time": None,
499
483
  "utilization_policy": None,
500
484
  "name": "",
501
485
  "default": False,
@@ -526,7 +510,6 @@ class TestCreateFleet:
526
510
  "fleet_id": "1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e",
527
511
  "fleet_name": spec.configuration.name,
528
512
  "instance_num": 0,
529
- "pool_name": None,
530
513
  "job_name": None,
531
514
  "hostname": "1.1.1.1",
532
515
  "status": "pending",
@@ -627,12 +610,10 @@ class TestDeleteFleets:
627
610
  await add_project_member(
628
611
  session=session, project=project, user=user, project_role=ProjectRole.USER
629
612
  )
630
- pool = await create_pool(session=session, project=project)
631
613
  fleet = await create_fleet(session=session, project=project)
632
614
  instance = await create_instance(
633
615
  session=session,
634
616
  project=project,
635
- pool=pool,
636
617
  )
637
618
  fleet.instances.append(instance)
638
619
  await session.commit()
@@ -654,7 +635,6 @@ class TestDeleteFleets:
654
635
  ):
655
636
  user = await create_user(session, global_role=GlobalRole.USER)
656
637
  project = await create_project(session)
657
- pool = await create_pool(session=session, project=project)
658
638
  await add_project_member(
659
639
  session=session, project=project, user=user, project_role=ProjectRole.USER
660
640
  )
@@ -676,7 +656,6 @@ class TestDeleteFleets:
676
656
  instance = await create_instance(
677
657
  session=session,
678
658
  project=project,
679
- pool=pool,
680
659
  status=InstanceStatus.BUSY,
681
660
  job=job,
682
661
  )
@@ -744,18 +723,15 @@ class TestDeleteFleetInstances:
744
723
  await add_project_member(
745
724
  session=session, project=project, user=user, project_role=ProjectRole.USER
746
725
  )
747
- pool = await create_pool(session=session, project=project)
748
726
  fleet = await create_fleet(session=session, project=project)
749
727
  instance1 = await create_instance(
750
728
  session=session,
751
729
  project=project,
752
- pool=pool,
753
730
  instance_num=1,
754
731
  )
755
732
  instance2 = await create_instance(
756
733
  session=session,
757
734
  project=project,
758
- pool=pool,
759
735
  instance_num=2,
760
736
  )
761
737
  fleet.instances.append(instance1)
@@ -785,7 +761,6 @@ class TestDeleteFleetInstances:
785
761
  await add_project_member(
786
762
  session=session, project=project, user=user, project_role=ProjectRole.USER
787
763
  )
788
- pool = await create_pool(session=session, project=project)
789
764
  fleet = await create_fleet(session=session, project=project)
790
765
  repo = await create_repo(
791
766
  session=session,
@@ -804,7 +779,6 @@ class TestDeleteFleetInstances:
804
779
  instance = await create_instance(
805
780
  session=session,
806
781
  project=project,
807
- pool=pool,
808
782
  instance_num=1,
809
783
  status=InstanceStatus.BUSY,
810
784
  job=job,
@@ -13,6 +13,7 @@ from dstack._internal.server.services.gateways import (
13
13
  )
14
14
  from dstack._internal.server.services.projects import add_project_member
15
15
  from dstack._internal.server.testing.common import (
16
+ ComputeMockSpec,
16
17
  create_backend,
17
18
  create_gateway,
18
19
  create_gateway_compute,
@@ -174,7 +175,14 @@ class TestCreateGateway:
174
175
  backend = await create_backend(session, project.id, backend_type=BackendType.AWS)
175
176
  response = await client.post(
176
177
  f"/api/project/{project.name}/gateways/create",
177
- json={"name": "test", "backend_type": "aws", "region": "us"},
178
+ json={
179
+ "configuration": {
180
+ "type": "gateway",
181
+ "name": "test",
182
+ "backend": "aws",
183
+ "region": "us",
184
+ },
185
+ },
178
186
  headers=get_auth_headers(user.token),
179
187
  )
180
188
  assert response.status_code == 200
@@ -217,7 +225,14 @@ class TestCreateGateway:
217
225
  g.return_value = "random-name"
218
226
  response = await client.post(
219
227
  f"/api/project/{project.name}/gateways/create",
220
- json={"name": None, "backend_type": "aws", "region": "us"},
228
+ json={
229
+ "configuration": {
230
+ "type": "gateway",
231
+ "name": None,
232
+ "backend": "aws",
233
+ "region": "us",
234
+ },
235
+ },
221
236
  headers=get_auth_headers(user.token),
222
237
  )
223
238
  g.assert_called_once()
@@ -258,7 +273,14 @@ class TestCreateGateway:
258
273
  )
259
274
  response = await client.post(
260
275
  f"/api/project/{project.name}/gateways/create",
261
- json={"name": "test", "backend_type": "aws", "region": "us"},
276
+ json={
277
+ "configuration": {
278
+ "type": "gateway",
279
+ "name": "test",
280
+ "backend": "aws",
281
+ "region": "us",
282
+ },
283
+ },
262
284
  headers=get_auth_headers(user.token),
263
285
  )
264
286
  assert response.status_code == 400
@@ -437,8 +459,10 @@ class TestDeleteGateway:
437
459
  "dstack._internal.server.services.gateways.get_project_backend_by_type_or_error"
438
460
  ) as m:
439
461
  aws = Mock()
462
+ aws.compute.return_value = Mock(spec=ComputeMockSpec)
440
463
  aws.compute.return_value.terminate_gateway.return_value = None # success
441
464
  gcp = Mock()
465
+ gcp.compute.return_value = Mock(spec=ComputeMockSpec)
442
466
  gcp.compute.return_value.terminate_gateway.side_effect = DstackError() # fail
443
467
 
444
468
  def get_backend(project, backend_type):
@@ -15,7 +15,6 @@ from dstack._internal.server.services.projects import add_project_member
15
15
  from dstack._internal.server.testing.common import (
16
16
  create_fleet,
17
17
  create_instance,
18
- create_pool,
19
18
  create_project,
20
19
  create_user,
21
20
  get_auth_headers,
@@ -59,11 +58,6 @@ class TestListInstances:
59
58
  await add_project_member(
60
59
  session, project=projects[2], user=users[1], project_role=ProjectRole.USER
61
60
  )
62
- pools = [
63
- await create_pool(session, projects[0]),
64
- await create_pool(session, projects[1]),
65
- await create_pool(session, projects[2]),
66
- ]
67
61
  fleets = [
68
62
  await create_fleet(
69
63
  session,
@@ -88,7 +82,6 @@ class TestListInstances:
88
82
  await create_instance(
89
83
  session=session,
90
84
  project=projects[0],
91
- pool=pools[0],
92
85
  fleet=fleets[0],
93
86
  created_at=dt.datetime(2024, 1, 1, tzinfo=dt.timezone.utc),
94
87
  name="fleet0-0",
@@ -96,7 +89,6 @@ class TestListInstances:
96
89
  await create_instance(
97
90
  session=session,
98
91
  project=projects[1],
99
- pool=pools[1],
100
92
  fleet=fleets[1],
101
93
  created_at=dt.datetime(2024, 1, 2, tzinfo=dt.timezone.utc),
102
94
  name="fleet1-0",
@@ -104,7 +96,6 @@ class TestListInstances:
104
96
  await create_instance(
105
97
  session=session,
106
98
  project=projects[2],
107
- pool=pools[2],
108
99
  fleet=fleets[2],
109
100
  created_at=dt.datetime(2024, 1, 3, tzinfo=dt.timezone.utc),
110
101
  name="fleet2-0",
@@ -112,7 +103,6 @@ class TestListInstances:
112
103
  await create_instance(
113
104
  session=session,
114
105
  project=projects[2],
115
- pool=pools[2],
116
106
  fleet=fleets[2],
117
107
  created_at=dt.datetime(2024, 1, 4, tzinfo=dt.timezone.utc),
118
108
  instance_num=1,
@@ -14,6 +14,9 @@ from dstack._internal.server.testing.common import (
14
14
  create_run,
15
15
  create_user,
16
16
  get_auth_headers,
17
+ get_instance_offer_with_availability,
18
+ get_job_provisioning_data,
19
+ get_job_runtime_data,
17
20
  )
18
21
 
19
22
  pytestmark = pytest.mark.usefixtures("image_config_mock")
@@ -51,9 +54,18 @@ class TestGetJobMetrics:
51
54
  repo=repo,
52
55
  user=user,
53
56
  )
57
+ jpd = get_job_provisioning_data(
58
+ cpu_count=128, memory_gib=256, gpu_count=2, gpu_memory_gib=32
59
+ )
60
+ offer = get_instance_offer_with_availability(
61
+ cpu_count=64, memory_gib=128, gpu_count=1, gpu_memory_gib=32
62
+ )
63
+ jrd = get_job_runtime_data(offer=offer)
54
64
  job = await create_job(
55
65
  session=session,
56
66
  run=run,
67
+ job_provisioning_data=jpd,
68
+ job_runtime_data=jrd,
57
69
  )
58
70
  await create_job_metrics_point(
59
71
  session=session,
@@ -108,11 +120,26 @@ class TestGetJobMetrics:
108
120
  "timestamps": ["2023-01-02T03:04:25+00:00"],
109
121
  "values": [512],
110
122
  },
123
+ {
124
+ "name": "cpus_detected_num",
125
+ "timestamps": ["2023-01-02T03:04:25+00:00"],
126
+ "values": [64],
127
+ },
128
+ {
129
+ "name": "memory_total_bytes",
130
+ "timestamps": ["2023-01-02T03:04:25+00:00"],
131
+ "values": [137438953472],
132
+ },
111
133
  {
112
134
  "name": "gpus_detected_num",
113
135
  "timestamps": ["2023-01-02T03:04:25+00:00"],
114
136
  "values": [1],
115
137
  },
138
+ {
139
+ "name": "gpu_memory_total_bytes",
140
+ "timestamps": ["2023-01-02T03:04:25+00:00"],
141
+ "values": [34359738368],
142
+ },
116
143
  {
117
144
  "name": "gpu_memory_usage_bytes_gpu0",
118
145
  "timestamps": ["2023-01-02T03:04:25+00:00"],
@@ -503,6 +503,62 @@ class TestSetProjectMembers:
503
503
  members = res.scalars().all()
504
504
  assert len(members) == 3
505
505
 
506
+ @pytest.mark.asyncio
507
+ @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
508
+ async def test_sets_project_members_by_email(
509
+ self, test_db, session: AsyncSession, client: AsyncClient
510
+ ):
511
+ project = await create_project(
512
+ session=session,
513
+ created_at=datetime(2023, 1, 2, 3, 4, tzinfo=timezone.utc),
514
+ )
515
+ admin = await create_user(
516
+ session=session,
517
+ created_at=datetime(2023, 1, 2, 3, 4, tzinfo=timezone.utc),
518
+ global_role=GlobalRole.ADMIN,
519
+ )
520
+ user1 = await create_user(
521
+ session=session,
522
+ name="user1",
523
+ created_at=datetime(2023, 1, 2, 3, 4, tzinfo=timezone.utc),
524
+ email="testemail@example.com",
525
+ )
526
+ members = [
527
+ {
528
+ "username": user1.email,
529
+ "project_role": ProjectRole.ADMIN,
530
+ },
531
+ ]
532
+ body = {"members": members}
533
+ response = await client.post(
534
+ f"/api/projects/{project.name}/set_members",
535
+ headers=get_auth_headers(admin.token),
536
+ json=body,
537
+ )
538
+ assert response.status_code == 200, response.json()
539
+ assert response.json()["members"] == [
540
+ {
541
+ "user": {
542
+ "id": str(user1.id),
543
+ "username": user1.name,
544
+ "created_at": "2023-01-02T03:04:00+00:00",
545
+ "global_role": user1.global_role,
546
+ "email": user1.email,
547
+ "active": True,
548
+ "permissions": {
549
+ "can_create_projects": True,
550
+ },
551
+ },
552
+ "project_role": ProjectRole.ADMIN,
553
+ "permissions": {
554
+ "can_manage_ssh_fleets": True,
555
+ },
556
+ },
557
+ ]
558
+ res = await session.execute(select(MemberModel))
559
+ members = res.scalars().all()
560
+ assert len(members) == 1
561
+
506
562
  @pytest.mark.asyncio
507
563
  @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
508
564
  async def test_manager_cannot_set_project_admins(
@@ -1,20 +1,31 @@
1
+ from datetime import datetime, timedelta, timezone
1
2
  from textwrap import dedent
3
+ from typing import Optional
2
4
 
3
5
  import pytest
6
+ from freezegun import freeze_time
4
7
  from httpx import AsyncClient
5
8
  from sqlalchemy.ext.asyncio import AsyncSession
6
9
 
7
- from dstack._internal.core.models.runs import JobStatus
10
+ from dstack._internal.core.models.backends.base import BackendType
11
+ from dstack._internal.core.models.configurations import DevEnvironmentConfiguration
12
+ from dstack._internal.core.models.runs import JobProvisioningData, JobRuntimeData, JobStatus
8
13
  from dstack._internal.core.models.users import GlobalRole, ProjectRole
9
14
  from dstack._internal.server.models import JobModel, ProjectModel, UserModel
10
15
  from dstack._internal.server.services.projects import add_project_member
11
16
  from dstack._internal.server.testing.common import (
17
+ create_fleet,
18
+ create_instance,
12
19
  create_job,
13
20
  create_job_prometheus_metrics,
14
21
  create_project,
15
22
  create_repo,
16
23
  create_run,
17
24
  create_user,
25
+ get_instance_offer_with_availability,
26
+ get_job_provisioning_data,
27
+ get_job_runtime_data,
28
+ get_run_spec,
18
29
  )
19
30
 
20
31
 
@@ -23,14 +34,32 @@ def enable_metrics(monkeypatch: pytest.MonkeyPatch):
23
34
  monkeypatch.setattr("dstack._internal.server.settings.ENABLE_PROMETHEUS_METRICS", True)
24
35
 
25
36
 
37
+ FAKE_NOW = datetime(2023, 1, 2, 3, 4, tzinfo=timezone.utc)
38
+
39
+
40
+ @freeze_time(FAKE_NOW)
26
41
  @pytest.mark.asyncio
27
42
  @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
28
43
  @pytest.mark.usefixtures("image_config_mock", "test_db", "enable_metrics")
29
44
  class TestGetPrometheusMetrics:
30
45
  async def test_returns_metrics(self, session: AsyncSession, client: AsyncClient):
31
- user = await create_user(session=session, global_role=GlobalRole.USER)
46
+ user = await create_user(session=session, name="test-user", global_role=GlobalRole.USER)
47
+ offer = get_instance_offer_with_availability(
48
+ instance_type="test-type", gpu_count=2, gpu_name="V4", price=12
49
+ )
32
50
  project_2 = await _create_project(session, "project-2", user)
33
- job_2_1 = await _create_job(session, "run-1", project_2, user, JobStatus.RUNNING)
51
+ jpd_2_1 = get_job_provisioning_data(
52
+ backend=BackendType.AWS, gpu_name="T4", gpu_count=2, price=16
53
+ )
54
+ job_2_1 = await _create_job(
55
+ session=session,
56
+ run_name="run-1",
57
+ project=project_2,
58
+ user=user,
59
+ status=JobStatus.RUNNING,
60
+ job_provisioning_data=jpd_2_1,
61
+ submitted_at=FAKE_NOW - timedelta(seconds=100),
62
+ )
34
63
  await create_job_prometheus_metrics(
35
64
  session=session,
36
65
  job=job_2_1,
@@ -42,7 +71,18 @@ class TestGetPrometheusMetrics:
42
71
  """),
43
72
  )
44
73
  project_1 = await _create_project(session, "project-1", user)
45
- job_1_1 = await _create_job(session, "run-1", project_1, user, JobStatus.RUNNING)
74
+ jpd_1_1 = get_job_provisioning_data(backend=BackendType.AWS, gpu_count=4, gpu_name="T4")
75
+ jrd_1_1 = get_job_runtime_data(offer=offer)
76
+ job_1_1 = await _create_job(
77
+ session=session,
78
+ run_name="run-1",
79
+ project=project_1,
80
+ user=user,
81
+ status=JobStatus.RUNNING,
82
+ job_provisioning_data=jpd_1_1,
83
+ job_runtime_data=jrd_1_1,
84
+ submitted_at=FAKE_NOW - timedelta(seconds=120),
85
+ )
46
86
  await create_job_prometheus_metrics(
47
87
  session=session,
48
88
  job=job_1_1,
@@ -84,30 +124,62 @@ class TestGetPrometheusMetrics:
84
124
  FIELD_1{gpu="1"} 20
85
125
  """),
86
126
  )
127
+ fleet = await create_fleet(session=session, project=project_1, name="test-fleet")
128
+ instance = await create_instance(
129
+ session=session,
130
+ project=project_1,
131
+ fleet=fleet,
132
+ backend=BackendType.AWS,
133
+ offer=offer,
134
+ price=14,
135
+ created_at=FAKE_NOW - timedelta(hours=1),
136
+ name="test-instance",
137
+ )
87
138
 
88
139
  response = await client.get("/metrics")
89
140
 
90
141
  assert response.status_code == 200
91
- assert response.text == dedent("""\
142
+ assert response.text == dedent(f"""\
143
+ # HELP dstack_instance_duration_seconds_total Total seconds the instance is running
144
+ # TYPE dstack_instance_duration_seconds_total counter
145
+ dstack_instance_duration_seconds_total{{dstack_project_name="project-1",dstack_fleet_name="test-fleet",dstack_fleet_id="{fleet.id}",dstack_instance_name="test-instance",dstack_instance_id="{instance.id}",dstack_instance_type="test-type",dstack_backend="aws",dstack_gpu="V4"}} 3600.0
146
+ # HELP dstack_instance_price_dollars_per_hour Instance price, USD/hour
147
+ # TYPE dstack_instance_price_dollars_per_hour gauge
148
+ dstack_instance_price_dollars_per_hour{{dstack_project_name="project-1",dstack_fleet_name="test-fleet",dstack_fleet_id="{fleet.id}",dstack_instance_name="test-instance",dstack_instance_id="{instance.id}",dstack_instance_type="test-type",dstack_backend="aws",dstack_gpu="V4"}} 14.0
149
+ # HELP dstack_instance_gpu_count Instance GPU count
150
+ # TYPE dstack_instance_gpu_count gauge
151
+ dstack_instance_gpu_count{{dstack_project_name="project-1",dstack_fleet_name="test-fleet",dstack_fleet_id="{fleet.id}",dstack_instance_name="test-instance",dstack_instance_id="{instance.id}",dstack_instance_type="test-type",dstack_backend="aws",dstack_gpu="V4"}} 2.0
152
+ # HELP dstack_job_duration_seconds_total Total seconds the job is running
153
+ # TYPE dstack_job_duration_seconds_total counter
154
+ dstack_job_duration_seconds_total{{dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0",dstack_run_type="dev-environment",dstack_backend="aws",dstack_gpu="V4"}} 120.0
155
+ dstack_job_duration_seconds_total{{dstack_project_name="project-2",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_2_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_2_1.id}",dstack_job_num="0",dstack_replica_num="0",dstack_run_type="dev-environment",dstack_backend="aws",dstack_gpu="T4"}} 100.0
156
+ # HELP dstack_job_price_dollars_per_hour Job instance price, USD/hour
157
+ # TYPE dstack_job_price_dollars_per_hour gauge
158
+ dstack_job_price_dollars_per_hour{{dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0",dstack_run_type="dev-environment",dstack_backend="aws",dstack_gpu="V4"}} 12.0
159
+ dstack_job_price_dollars_per_hour{{dstack_project_name="project-2",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_2_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_2_1.id}",dstack_job_num="0",dstack_replica_num="0",dstack_run_type="dev-environment",dstack_backend="aws",dstack_gpu="T4"}} 16.0
160
+ # HELP dstack_job_gpu_count Job GPU count
161
+ # TYPE dstack_job_gpu_count gauge
162
+ dstack_job_gpu_count{{dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0",dstack_run_type="dev-environment",dstack_backend="aws",dstack_gpu="V4"}} 2.0
163
+ dstack_job_gpu_count{{dstack_project_name="project-2",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_2_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_2_1.id}",dstack_job_num="0",dstack_replica_num="0",dstack_run_type="dev-environment",dstack_backend="aws",dstack_gpu="T4"}} 2.0
92
164
  # HELP FIELD_1 Test field 1
93
165
  # TYPE FIELD_1 gauge
94
- FIELD_1{gpu="0",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 350.0
95
- FIELD_1{gpu="1",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 400.0
96
- FIELD_1{gpu="0",dstack_project_name="project-1",dstack_run_name="run-2",dstack_job_name="run-2-0-0",dstack_job_num="0",dstack_replica_num="0"} 1200.0
97
- FIELD_1{gpu="1",dstack_project_name="project-1",dstack_run_name="run-2",dstack_job_name="run-2-0-0",dstack_job_num="0",dstack_replica_num="0"} 1600.0
98
- FIELD_1{gpu="2",dstack_project_name="project-1",dstack_run_name="run-2",dstack_job_name="run-2-0-0",dstack_job_num="0",dstack_replica_num="0"} 2400.0
99
- FIELD_1{gpu="0",dstack_project_name="project-2",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 100.0
100
- FIELD_1{gpu="1",dstack_project_name="project-2",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 200.0
166
+ FIELD_1{{gpu="0",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 350.0
167
+ FIELD_1{{gpu="1",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 400.0
168
+ FIELD_1{{gpu="0",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-2",dstack_run_id="{job_1_2.run_id}",dstack_job_name="run-2-0-0",dstack_job_id="{job_1_2.id}",dstack_job_num="0",dstack_replica_num="0"}} 1200.0
169
+ FIELD_1{{gpu="1",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-2",dstack_run_id="{job_1_2.run_id}",dstack_job_name="run-2-0-0",dstack_job_id="{job_1_2.id}",dstack_job_num="0",dstack_replica_num="0"}} 1600.0
170
+ FIELD_1{{gpu="2",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-2",dstack_run_id="{job_1_2.run_id}",dstack_job_name="run-2-0-0",dstack_job_id="{job_1_2.id}",dstack_job_num="0",dstack_replica_num="0"}} 2400.0
171
+ FIELD_1{{gpu="0",dstack_project_name="project-2",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_2_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_2_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 100.0
172
+ FIELD_1{{gpu="1",dstack_project_name="project-2",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_2_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_2_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 200.0
101
173
  # HELP FIELD_2 Test field 2
102
174
  # TYPE FIELD_2 counter
103
- FIELD_2{gpu="0",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 337325.0 1395066363000
104
- FIELD_2{gpu="1",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 987169.0 1395066363010
175
+ FIELD_2{{gpu="0",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 337325.0 1395066363000
176
+ FIELD_2{{gpu="1",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 987169.0 1395066363010
105
177
  """)
106
178
 
107
179
  async def test_returns_empty_response_if_no_runs(self, client: AsyncClient):
108
180
  response = await client.get("/metrics")
109
181
  assert response.status_code == 200
110
- assert response.text == ""
182
+ assert response.text == "\n"
111
183
 
112
184
  async def test_returns_404_if_not_enabled(
113
185
  self, monkeypatch: pytest.MonkeyPatch, client: AsyncClient
@@ -122,7 +194,7 @@ class TestGetPrometheusMetrics:
122
194
  @pytest.mark.usefixtures("image_config_mock", "test_db", "enable_metrics")
123
195
  class TestGetPrometheusProjectMetrics:
124
196
  async def test_returns_metrics(self, session: AsyncSession, client: AsyncClient):
125
- user = await create_user(session=session, global_role=GlobalRole.USER)
197
+ user = await create_user(session=session, name="test-user", global_role=GlobalRole.USER)
126
198
  project = await _create_project(session, "project-1", user)
127
199
  job_1 = await _create_job(session, "run-1", project, user, JobStatus.RUNNING)
128
200
  await create_job_prometheus_metrics(
@@ -184,18 +256,18 @@ class TestGetPrometheusProjectMetrics:
184
256
  response = await client.get("/metrics/project/project-1")
185
257
 
186
258
  assert response.status_code == 200
187
- assert response.text == dedent("""\
259
+ assert response.text == dedent(f"""\
188
260
  # HELP FIELD_1 Test field 1
189
261
  # TYPE FIELD_1 gauge
190
- FIELD_1{gpu="0",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 350.0
191
- FIELD_1{gpu="1",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 400.0
192
- FIELD_1{gpu="0",dstack_project_name="project-1",dstack_run_name="run-2",dstack_job_name="run-2-0-0",dstack_job_num="0",dstack_replica_num="0"} 1200.0
193
- FIELD_1{gpu="1",dstack_project_name="project-1",dstack_run_name="run-2",dstack_job_name="run-2-0-0",dstack_job_num="0",dstack_replica_num="0"} 1600.0
194
- FIELD_1{gpu="2",dstack_project_name="project-1",dstack_run_name="run-2",dstack_job_name="run-2-0-0",dstack_job_num="0",dstack_replica_num="0"} 2400.0
262
+ FIELD_1{{gpu="0",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 350.0
263
+ FIELD_1{{gpu="1",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 400.0
264
+ FIELD_1{{gpu="0",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-2",dstack_run_id="{job_2.run_id}",dstack_job_name="run-2-0-0",dstack_job_id="{job_2.id}",dstack_job_num="0",dstack_replica_num="0"}} 1200.0
265
+ FIELD_1{{gpu="1",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-2",dstack_run_id="{job_2.run_id}",dstack_job_name="run-2-0-0",dstack_job_id="{job_2.id}",dstack_job_num="0",dstack_replica_num="0"}} 1600.0
266
+ FIELD_1{{gpu="2",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-2",dstack_run_id="{job_2.run_id}",dstack_job_name="run-2-0-0",dstack_job_id="{job_2.id}",dstack_job_num="0",dstack_replica_num="0"}} 2400.0
195
267
  # HELP FIELD_2 Test field 2
196
268
  # TYPE FIELD_2 counter
197
- FIELD_2{gpu="0",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 337325.0 1395066363000
198
- FIELD_2{gpu="1",dstack_project_name="project-1",dstack_run_name="run-1",dstack_job_name="run-1-0-0",dstack_job_num="0",dstack_replica_num="0"} 987169.0 1395066363010
269
+ FIELD_2{{gpu="0",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 337325.0 1395066363000
270
+ FIELD_2{{gpu="1",dstack_project_name="project-1",dstack_user_name="test-user",dstack_run_name="run-1",dstack_run_id="{job_1.run_id}",dstack_job_name="run-1-0-0",dstack_job_id="{job_1.id}",dstack_job_num="0",dstack_replica_num="0"}} 987169.0 1395066363010
199
271
  """)
200
272
 
201
273
  async def test_returns_empty_response_if_no_runs(
@@ -205,7 +277,7 @@ class TestGetPrometheusProjectMetrics:
205
277
  await create_project(session=session, owner=user, name="test-project")
206
278
  response = await client.get("/metrics/project/test-project")
207
279
  assert response.status_code == 200
208
- assert response.text == ""
280
+ assert response.text == "\n"
209
281
 
210
282
  async def test_returns_404_if_project_doesnt_exist(self, client: AsyncClient):
211
283
  response = await client.get("/metrics/project/nonexistent")
@@ -230,15 +302,32 @@ async def _create_project(session: AsyncSession, name: str, user: UserModel) ->
230
302
 
231
303
 
232
304
  async def _create_job(
233
- session: AsyncSession, run_name: str, project: ProjectModel, user: UserModel, status: JobStatus
305
+ session: AsyncSession,
306
+ run_name: str,
307
+ project: ProjectModel,
308
+ user: UserModel,
309
+ status: JobStatus,
310
+ job_provisioning_data: Optional[JobProvisioningData] = None,
311
+ job_runtime_data: Optional[JobRuntimeData] = None,
312
+ submitted_at: datetime = FAKE_NOW,
234
313
  ) -> JobModel:
235
314
  repo = await create_repo(session=session, project_id=project.id, repo_name=f"{run_name}-repo")
315
+ configuration = DevEnvironmentConfiguration(ide="vscode")
316
+ run_spec = get_run_spec(run_name=run_name, repo_id=repo.name, configuration=configuration)
236
317
  run = await create_run(
237
318
  session=session,
238
319
  project=project,
239
320
  repo=repo,
240
321
  user=user,
241
322
  run_name=run_name,
323
+ run_spec=run_spec,
324
+ )
325
+ job = await create_job(
326
+ session=session,
327
+ run=run,
328
+ status=status,
329
+ job_provisioning_data=job_provisioning_data,
330
+ job_runtime_data=job_runtime_data,
331
+ submitted_at=submitted_at,
242
332
  )
243
- job = await create_job(session=session, run=run, status=status)
244
333
  return job