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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dstack might be problematic. Click here for more details.

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-4a0fe83e84574654e397.js} +18 -14
  146. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js.map → main-4a0fe83e84574654e397.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.0.dist-info}/METADATA +1 -1
  164. {dstack-0.18.44.dist-info → dstack-0.19.0.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.0.dist-info}/LICENSE.md +0 -0
  265. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/WHEEL +0 -0
  266. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/entry_points.txt +0 -0
  267. {dstack-0.18.44.dist-info → dstack-0.19.0.dist-info}/top_level.txt +0 -0
@@ -1,612 +0,0 @@
1
- import datetime as dt
2
-
3
- import pytest
4
- from freezegun import freeze_time
5
- from httpx import AsyncClient
6
- from sqlalchemy import select
7
- from sqlalchemy.ext.asyncio import AsyncSession
8
-
9
- from dstack._internal.core.models.backends.base import BackendType
10
- from dstack._internal.core.models.instances import SSHKey
11
- from dstack._internal.core.models.profiles import DEFAULT_POOL_NAME
12
- from dstack._internal.core.models.users import GlobalRole, ProjectRole
13
- from dstack._internal.server.models import PoolModel
14
- from dstack._internal.server.schemas.pools import (
15
- CreatePoolRequest,
16
- DeletePoolRequest,
17
- RemoveInstanceRequest,
18
- SetDefaultPoolRequest,
19
- ShowPoolRequest,
20
- )
21
- from dstack._internal.server.schemas.runs import AddRemoteInstanceRequest
22
- from dstack._internal.server.services.projects import add_project_member
23
- from dstack._internal.server.testing.common import (
24
- create_instance,
25
- create_pool,
26
- create_project,
27
- create_user,
28
- get_auth_headers,
29
- )
30
-
31
- TEST_POOL_NAME = "test_router_pool_name"
32
-
33
-
34
- class TestListPools:
35
- @pytest.mark.asyncio
36
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
37
- async def test_returns_403_if_not_authenticated(
38
- self, test_db, session: AsyncSession, client: AsyncClient
39
- ):
40
- user = await create_user(session=session, global_role=GlobalRole.USER)
41
- project = await create_project(session=session, owner=user)
42
- response = await client.post(
43
- f"/api/project/{project.name}/pool/list",
44
- json={},
45
- )
46
- assert response.status_code == 403
47
-
48
- @pytest.mark.asyncio
49
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
50
- @freeze_time(dt.datetime(2023, 10, 4, 12, 0, tzinfo=dt.timezone.utc))
51
- async def test_creates_and_lists_default_pool(
52
- self, test_db, session: AsyncSession, client: AsyncClient
53
- ):
54
- user = await create_user(session=session, global_role=GlobalRole.USER)
55
- project = await create_project(session=session, owner=user)
56
- await add_project_member(
57
- session=session, project=project, user=user, project_role=ProjectRole.USER
58
- )
59
- response = await client.post(
60
- f"/api/project/{project.name}/pool/list",
61
- headers=get_auth_headers(user.token),
62
- json={},
63
- )
64
- assert response.status_code == 200
65
- result = response.json()
66
- expected = [
67
- {
68
- "name": "default-pool",
69
- "default": True,
70
- "created_at": "2023-10-04T12:00:00+00:00",
71
- "total_instances": 0,
72
- "available_instances": 0,
73
- }
74
- ]
75
- assert result == expected
76
-
77
-
78
- class TestDeletePool:
79
- @pytest.mark.asyncio
80
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
81
- async def test_returns_403_if_not_authenticated(
82
- self, test_db, session: AsyncSession, client: AsyncClient
83
- ):
84
- user = await create_user(session=session, global_role=GlobalRole.USER)
85
- project = await create_project(session=session, owner=user)
86
- response = await client.post(
87
- f"/api/project/{project.name}/pool/delete",
88
- json=DeletePoolRequest(name=TEST_POOL_NAME, force=False).dict(),
89
- )
90
- assert response.status_code == 403
91
-
92
- @pytest.mark.asyncio
93
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
94
- async def test_delete_last_pool(self, test_db, session: AsyncSession, client: AsyncClient):
95
- user = await create_user(session=session, global_role=GlobalRole.USER)
96
- project = await create_project(session=session, owner=user)
97
- await add_project_member(
98
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
99
- )
100
- pool = await create_pool(session, project, pool_name=TEST_POOL_NAME)
101
- response = await client.post(
102
- f"/api/project/{project.name}/pool/delete",
103
- headers=get_auth_headers(user.token),
104
- json=DeletePoolRequest(name=TEST_POOL_NAME, force=False).dict(),
105
- )
106
- assert response.status_code == 200
107
- assert response.json() is None
108
-
109
- response = await client.post(
110
- f"/api/project/{project.name}/pool/list",
111
- headers=get_auth_headers(user.token),
112
- json={},
113
- )
114
- assert response.status_code == 200
115
-
116
- result = response.json()
117
- assert len(result) == 1
118
-
119
- default_pool = result[0]
120
- assert default_pool["name"] == DEFAULT_POOL_NAME
121
- assert dt.datetime.fromisoformat(default_pool["created_at"]) > pool.created_at
122
-
123
- @pytest.mark.asyncio
124
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
125
- async def test_deletes_pool(self, test_db, session: AsyncSession, client: AsyncClient):
126
- user = await create_user(session=session, global_role=GlobalRole.USER)
127
- project = await create_project(session=session, owner=user)
128
- await add_project_member(
129
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
130
- )
131
- pool1 = await create_pool(session, project, pool_name=f"{TEST_POOL_NAME}-left")
132
- pool2 = await create_pool(session, project, pool_name=f"{TEST_POOL_NAME}-right")
133
- response = await client.post(
134
- f"/api/project/{project.name}/pool/delete",
135
- headers=get_auth_headers(user.token),
136
- json=DeletePoolRequest(name=pool1.name, force=False).dict(),
137
- )
138
- assert response.status_code == 200
139
- assert response.json() is None
140
- res = await session.execute(select(PoolModel).where(PoolModel.deleted == False))
141
- pool = res.scalar_one()
142
- assert pool.name == pool2.name
143
-
144
- @pytest.mark.asyncio
145
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
146
- async def test_returns_400_if_pool_missing(
147
- self, test_db, session: AsyncSession, client: AsyncClient
148
- ):
149
- user = await create_user(session=session, global_role=GlobalRole.USER)
150
- project = await create_project(session=session, owner=user)
151
- await add_project_member(
152
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
153
- )
154
- response = await client.post(
155
- f"/api/project/{project.name}/pool/delete",
156
- headers=get_auth_headers(user.token),
157
- json=DeletePoolRequest(name="missing name", force=False).dict(),
158
- )
159
- assert response.status_code == 400
160
-
161
-
162
- class TestSetDefaultPool:
163
- @pytest.mark.asyncio
164
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
165
- async def test_returns_403_if_not_authenticated(
166
- self, test_db, session: AsyncSession, client: AsyncClient
167
- ):
168
- user = await create_user(session=session, global_role=GlobalRole.USER)
169
- project = await create_project(session=session, owner=user)
170
- response = await client.post(
171
- f"/api/project/{project.name}/pool/set_default",
172
- json=SetDefaultPoolRequest(pool_name=TEST_POOL_NAME).dict(),
173
- )
174
- assert response.status_code == 403
175
-
176
- @pytest.mark.asyncio
177
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
178
- async def test_sets_default(self, test_db, session: AsyncSession, client: AsyncClient):
179
- user = await create_user(session=session, global_role=GlobalRole.USER)
180
- project = await create_project(session=session, owner=user)
181
- await add_project_member(
182
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
183
- )
184
- pool = await create_pool(session, project, pool_name=f"{TEST_POOL_NAME}-right")
185
- response = await client.post(
186
- f"/api/project/{project.name}/pool/set_default",
187
- headers=get_auth_headers(user.token),
188
- json=SetDefaultPoolRequest(pool_name=pool.name).dict(),
189
- )
190
- assert response.status_code == 200
191
- await session.refresh(project)
192
- assert project.default_pool_id == pool.id
193
-
194
- @pytest.mark.asyncio
195
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
196
- async def test_returns_400_if_pool_missing(
197
- self, test_db, session: AsyncSession, client: AsyncClient
198
- ):
199
- user = await create_user(session=session, global_role=GlobalRole.USER)
200
- project = await create_project(session=session, owner=user)
201
- await add_project_member(
202
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
203
- )
204
- response = await client.post(
205
- f"/api/project/{project.name}/pool/set_default",
206
- headers=get_auth_headers(user.token),
207
- json=SetDefaultPoolRequest(pool_name="missing pool").dict(),
208
- )
209
- assert response.status_code == 400
210
-
211
-
212
- class TestCreatePool:
213
- @pytest.mark.asyncio
214
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
215
- async def test_returns_403_if_not_authenticated(
216
- self, test_db, session: AsyncSession, client: AsyncClient
217
- ):
218
- user = await create_user(session=session, global_role=GlobalRole.USER)
219
- project = await create_project(session=session, owner=user)
220
- response = await client.post(
221
- f"/api/project/{project.name}/pool/create",
222
- json=CreatePoolRequest(name=TEST_POOL_NAME).dict(),
223
- )
224
- assert response.status_code == 403
225
-
226
- @pytest.mark.asyncio
227
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
228
- async def test_create_pool(self, test_db, session: AsyncSession, client: AsyncClient):
229
- user = await create_user(session=session, global_role=GlobalRole.USER)
230
- project = await create_project(session=session, owner=user)
231
- await add_project_member(
232
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
233
- )
234
- response = await client.post(
235
- f"/api/project/{project.name}/pool/create",
236
- headers=get_auth_headers(user.token),
237
- json=CreatePoolRequest(name=TEST_POOL_NAME).dict(),
238
- )
239
- assert response.status_code == 200
240
- assert response.json() is None
241
- res = await session.execute(select(PoolModel).where(PoolModel.deleted == False))
242
- res.scalar_one()
243
-
244
- @pytest.mark.asyncio
245
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
246
- async def test_returns_400_on_duplicate_name(
247
- self, test_db, session: AsyncSession, client: AsyncClient
248
- ):
249
- user = await create_user(session=session, global_role=GlobalRole.USER)
250
- project = await create_project(session=session, owner=user)
251
- await add_project_member(
252
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
253
- )
254
- response = await client.post(
255
- f"/api/project/{project.name}/pool/create",
256
- headers=get_auth_headers(user.token),
257
- json=CreatePoolRequest(name=TEST_POOL_NAME).dict(),
258
- )
259
- assert response.status_code == 200
260
- assert response.json() is None
261
- response = await client.post(
262
- f"/api/project/{project.name}/pool/create",
263
- headers=get_auth_headers(user.token),
264
- json=CreatePoolRequest(name=TEST_POOL_NAME).dict(),
265
- )
266
- assert response.status_code == 400
267
-
268
-
269
- class TestShowPool:
270
- @pytest.mark.asyncio
271
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
272
- async def test_returns_403_if_not_authenticated(
273
- self, test_db, session: AsyncSession, client: AsyncClient
274
- ):
275
- user = await create_user(session=session, global_role=GlobalRole.USER)
276
- project = await create_project(session=session, owner=user)
277
- response = await client.post(
278
- f"/api/project/{project.name}/pool/show",
279
- json=CreatePoolRequest(name=TEST_POOL_NAME).dict(),
280
- )
281
- assert response.status_code == 403
282
-
283
- @pytest.mark.asyncio
284
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
285
- async def test_show_pool(self, test_db, session: AsyncSession, client: AsyncClient):
286
- user = await create_user(session=session, global_role=GlobalRole.USER)
287
- project = await create_project(session=session, owner=user)
288
- await add_project_member(
289
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
290
- )
291
- pool = await create_pool(session, project, pool_name=TEST_POOL_NAME)
292
- instance = await create_instance(
293
- session=session,
294
- project=project,
295
- pool=pool,
296
- backend=BackendType.DATACRUNCH,
297
- region="en",
298
- )
299
- response = await client.post(
300
- f"/api/project/{project.name}/pool/show",
301
- headers=get_auth_headers(user.token),
302
- json=ShowPoolRequest(name=TEST_POOL_NAME).dict(),
303
- )
304
- assert response.status_code == 200
305
- assert response.json() == {
306
- "name": "test_router_pool_name",
307
- "instances": [
308
- {
309
- "backend": "datacrunch",
310
- "instance_type": {
311
- "name": "instance",
312
- "resources": {
313
- "cpus": 1,
314
- "memory_mib": 512,
315
- "gpus": [],
316
- "spot": False,
317
- "disk": {"size_mib": 102400},
318
- "description": "",
319
- },
320
- },
321
- "id": str(instance.id),
322
- "project_name": project.name,
323
- "name": "test_instance",
324
- "fleet_id": None,
325
- "fleet_name": None,
326
- "instance_num": 0,
327
- "job_name": None,
328
- "hostname": "running_instance.ip",
329
- "status": "idle",
330
- "unreachable": False,
331
- "termination_reason": None,
332
- "created": "2023-01-02T03:04:00+00:00",
333
- "pool_name": None,
334
- "region": "en",
335
- "availability_zone": None,
336
- "price": 1,
337
- "total_blocks": 1,
338
- "busy_blocks": 0,
339
- }
340
- ],
341
- }
342
-
343
- @pytest.mark.asyncio
344
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
345
- async def test_show_missing_pool(self, test_db, session: AsyncSession, client: AsyncClient):
346
- user = await create_user(session=session, global_role=GlobalRole.USER)
347
- project = await create_project(session=session, owner=user)
348
- await add_project_member(
349
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
350
- )
351
- pool = await create_pool(session, project, pool_name=TEST_POOL_NAME)
352
- await create_instance(
353
- session=session,
354
- project=project,
355
- pool=pool,
356
- )
357
- response = await client.post(
358
- f"/api/project/{project.name}/pool/show",
359
- headers=get_auth_headers(user.token),
360
- json=ShowPoolRequest(name="missing_pool").dict(),
361
- )
362
- assert response.status_code == 400
363
- assert response.json() == {
364
- "detail": [{"msg": "Pool not found", "code": "resource_not_exists"}]
365
- }
366
-
367
-
368
- class TestAddRemote:
369
- @pytest.mark.asyncio
370
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
371
- async def test_returns_403_if_not_authenticated(
372
- self, test_db, session: AsyncSession, client: AsyncClient
373
- ):
374
- user = await create_user(session=session, global_role=GlobalRole.USER)
375
- project = await create_project(session=session, owner=user)
376
- remote = AddRemoteInstanceRequest(
377
- instance_name="test_instance_name",
378
- instance_network=None,
379
- region="",
380
- host="localhost",
381
- port=22,
382
- pool_name="pool_name",
383
- ssh_user="user",
384
- ssh_keys=[SSHKey(public="abc")],
385
- )
386
- response = await client.post(
387
- f"/api/project/{project.name}/pool/add_remote",
388
- json=remote.dict(),
389
- )
390
- assert response.status_code == 403
391
-
392
- @pytest.mark.asyncio
393
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
394
- async def test_add_remote(self, test_db, session: AsyncSession, client: AsyncClient):
395
- user = await create_user(session=session, global_role=GlobalRole.USER)
396
- project = await create_project(session=session, owner=user)
397
- await add_project_member(
398
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
399
- )
400
- remote = AddRemoteInstanceRequest(
401
- instance_name="test_instance_name",
402
- instance_network=None,
403
- region="",
404
- host="localhost",
405
- port=22,
406
- pool_name="pool_name",
407
- ssh_user="user",
408
- ssh_keys=[SSHKey(public="abc")],
409
- )
410
- response = await client.post(
411
- f"/api/project/{project.name}/pool/add_remote",
412
- headers=get_auth_headers(user.token),
413
- json=remote.dict(),
414
- )
415
- assert response.status_code == 200
416
-
417
- data = response.json()
418
- assert data["status"] == "pending"
419
- assert data["name"] == "test_instance_name"
420
-
421
-
422
- class TestRemoveInstance:
423
- @pytest.mark.asyncio
424
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
425
- async def test_returns_403_if_not_authenticated(
426
- self, test_db, session: AsyncSession, client: AsyncClient
427
- ):
428
- user = await create_user(session=session, global_role=GlobalRole.USER)
429
- project = await create_project(session=session, owner=user)
430
- remote = AddRemoteInstanceRequest(
431
- instance_name="test_instance_name",
432
- instance_network=None,
433
- region="",
434
- host="localhost",
435
- port=22,
436
- pool_name="pool_name",
437
- ssh_user="user",
438
- ssh_keys=[SSHKey(public="abc")],
439
- )
440
- response = await client.post(
441
- f"/api/project/{project.name}/pool/add_remote",
442
- json=remote.dict(),
443
- )
444
- assert response.status_code == 403
445
-
446
- @pytest.mark.asyncio
447
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
448
- async def test_remove_instance(self, test_db, session: AsyncSession, client: AsyncClient):
449
- user = await create_user(session=session, global_role=GlobalRole.USER)
450
- project = await create_project(session=session, owner=user)
451
- await add_project_member(
452
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
453
- )
454
- pool = await create_pool(session, project, pool_name=TEST_POOL_NAME)
455
- instance = await create_instance(
456
- session=session,
457
- project=project,
458
- pool=pool,
459
- backend=BackendType.DATACRUNCH,
460
- region="en",
461
- )
462
- response = await client.post(
463
- f"/api/project/{project.name}/pool/remove",
464
- headers=get_auth_headers(user.token),
465
- json=RemoveInstanceRequest(
466
- pool_name=TEST_POOL_NAME,
467
- instance_name=instance.name,
468
- ).dict(),
469
- )
470
- assert response.status_code == 200
471
- assert response.json() is None
472
-
473
- response = await client.post(
474
- f"/api/project/{project.name}/pool/show",
475
- headers=get_auth_headers(user.token),
476
- json=ShowPoolRequest(name=TEST_POOL_NAME).dict(),
477
- )
478
- assert response.status_code == 200
479
- assert response.json() == {
480
- "name": "test_router_pool_name",
481
- "instances": [
482
- {
483
- "backend": "datacrunch",
484
- "instance_type": {
485
- "name": "instance",
486
- "resources": {
487
- "cpus": 1,
488
- "memory_mib": 512,
489
- "gpus": [],
490
- "spot": False,
491
- "disk": {"size_mib": 102400},
492
- "description": "",
493
- },
494
- },
495
- "id": str(instance.id),
496
- "project_name": project.name,
497
- "name": "test_instance",
498
- "fleet_id": None,
499
- "fleet_name": None,
500
- "instance_num": 0,
501
- "job_name": None,
502
- "hostname": "running_instance.ip",
503
- "status": "terminating",
504
- "unreachable": False,
505
- "termination_reason": None,
506
- "created": "2023-01-02T03:04:00+00:00",
507
- "pool_name": None,
508
- "region": "en",
509
- "availability_zone": None,
510
- "price": 1,
511
- "total_blocks": 1,
512
- "busy_blocks": 0,
513
- }
514
- ],
515
- }
516
-
517
-
518
- class TestListInstances:
519
- @pytest.mark.asyncio
520
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
521
- async def test_returns_403_if_not_authenticated(
522
- self, test_db, session: AsyncSession, client: AsyncClient
523
- ):
524
- response = await client.post(
525
- "/api/pools/list_instances",
526
- json={},
527
- )
528
- assert response.status_code == 403
529
-
530
- @pytest.mark.asyncio
531
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
532
- async def test_lists_instances(self, test_db, session: AsyncSession, client: AsyncClient):
533
- user = await create_user(session=session, global_role=GlobalRole.USER)
534
- project = await create_project(session=session, owner=user)
535
- await add_project_member(
536
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
537
- )
538
- pool = await create_pool(session, project, pool_name=TEST_POOL_NAME)
539
- instance1 = await create_instance(
540
- session=session,
541
- project=project,
542
- pool=pool,
543
- created_at=dt.datetime(2023, 10, 4, 12, 0, tzinfo=dt.timezone.utc),
544
- )
545
- instance2 = await create_instance(
546
- session=session,
547
- project=project,
548
- pool=pool,
549
- created_at=dt.datetime(2023, 10, 5, 12, 0, tzinfo=dt.timezone.utc),
550
- )
551
- response = await client.post(
552
- "/api/pools/list_instances",
553
- headers=get_auth_headers(user.token),
554
- json={},
555
- )
556
- assert response.status_code == 200
557
- response_json = response.json()
558
- assert len(response_json) == 2
559
- assert response_json[0]["id"] == str(instance2.id)
560
- assert response_json[1]["id"] == str(instance1.id)
561
-
562
- @pytest.mark.asyncio
563
- @pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
564
- async def test_lists_paginated_instances(
565
- self, test_db, session: AsyncSession, client: AsyncClient
566
- ):
567
- user = await create_user(session=session, global_role=GlobalRole.USER)
568
- project = await create_project(session=session, owner=user)
569
- await add_project_member(
570
- session=session, project=project, user=user, project_role=ProjectRole.ADMIN
571
- )
572
- pool = await create_pool(session, project, pool_name=TEST_POOL_NAME)
573
- instance1 = await create_instance(
574
- session=session,
575
- project=project,
576
- pool=pool,
577
- created_at=dt.datetime(2023, 10, 5, 12, 0, tzinfo=dt.timezone.utc),
578
- )
579
- instance2 = await create_instance(
580
- session=session,
581
- project=project,
582
- pool=pool,
583
- created_at=dt.datetime(2023, 10, 3, 12, 0, tzinfo=dt.timezone.utc),
584
- )
585
- instance3 = await create_instance(
586
- session=session,
587
- project=project,
588
- pool=pool,
589
- created_at=dt.datetime(2023, 10, 6, 12, 0, tzinfo=dt.timezone.utc),
590
- )
591
- response = await client.post(
592
- "/api/pools/list_instances",
593
- headers=get_auth_headers(user.token),
594
- json={"limit": 2},
595
- )
596
- assert response.status_code == 200
597
- response_json = response.json()
598
- assert len(response_json) == 2
599
- assert response_json[0]["id"] == str(instance3.id)
600
- assert response_json[1]["id"] == str(instance1.id)
601
- response = await client.post(
602
- "/api/pools/list_instances",
603
- headers=get_auth_headers(user.token),
604
- json={
605
- "prev_id": response_json[1]["id"],
606
- "prev_created_at": response_json[1]["created"],
607
- },
608
- )
609
- assert response.status_code == 200
610
- response_json = response.json()
611
- assert len(response_json) == 1
612
- assert response_json[0]["id"] == str(instance2.id)