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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. dstack/_internal/cli/commands/gateway.py +15 -3
  2. dstack/_internal/cli/commands/logs.py +0 -22
  3. dstack/_internal/cli/commands/stats.py +8 -17
  4. dstack/_internal/cli/main.py +1 -5
  5. dstack/_internal/cli/services/configurators/fleet.py +4 -39
  6. dstack/_internal/cli/services/configurators/run.py +22 -20
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/cli/utils/run.py +11 -0
  10. dstack/_internal/core/backends/__init__.py +56 -39
  11. dstack/_internal/core/backends/aws/__init__.py +0 -25
  12. dstack/_internal/core/backends/aws/auth.py +1 -10
  13. dstack/_internal/core/backends/aws/backend.py +26 -0
  14. dstack/_internal/core/backends/aws/compute.py +21 -45
  15. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  16. dstack/_internal/core/backends/aws/models.py +135 -0
  17. dstack/_internal/core/backends/aws/resources.py +1 -1
  18. dstack/_internal/core/backends/azure/__init__.py +0 -20
  19. dstack/_internal/core/backends/azure/auth.py +2 -11
  20. dstack/_internal/core/backends/azure/backend.py +21 -0
  21. dstack/_internal/core/backends/azure/compute.py +14 -28
  22. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  23. dstack/_internal/core/backends/azure/models.py +89 -0
  24. dstack/_internal/core/backends/base/__init__.py +0 -12
  25. dstack/_internal/core/backends/base/backend.py +18 -0
  26. dstack/_internal/core/backends/base/compute.py +153 -33
  27. dstack/_internal/core/backends/base/configurator.py +105 -0
  28. dstack/_internal/core/backends/base/models.py +14 -0
  29. dstack/_internal/core/backends/configurators.py +138 -0
  30. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  31. dstack/_internal/core/backends/cudo/backend.py +16 -0
  32. dstack/_internal/core/backends/cudo/compute.py +8 -26
  33. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  34. dstack/_internal/core/backends/cudo/models.py +37 -0
  35. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  36. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  37. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  38. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  39. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  40. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  41. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  42. dstack/_internal/core/backends/gcp/auth.py +2 -11
  43. dstack/_internal/core/backends/gcp/backend.py +17 -0
  44. dstack/_internal/core/backends/gcp/compute.py +14 -44
  45. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  46. dstack/_internal/core/backends/gcp/models.py +125 -0
  47. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  48. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  49. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  50. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  51. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  52. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  53. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  54. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  55. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  56. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  57. dstack/_internal/core/backends/local/__init__.py +0 -13
  58. dstack/_internal/core/backends/local/backend.py +14 -0
  59. dstack/_internal/core/backends/local/compute.py +16 -2
  60. dstack/_internal/core/backends/models.py +128 -0
  61. dstack/_internal/core/backends/oci/__init__.py +0 -15
  62. dstack/_internal/core/backends/oci/auth.py +1 -5
  63. dstack/_internal/core/backends/oci/backend.py +16 -0
  64. dstack/_internal/core/backends/oci/compute.py +9 -23
  65. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  66. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  67. dstack/_internal/core/backends/oci/region.py +1 -1
  68. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  69. dstack/_internal/core/backends/runpod/backend.py +16 -0
  70. dstack/_internal/core/backends/runpod/compute.py +28 -6
  71. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  72. dstack/_internal/core/backends/runpod/models.py +54 -0
  73. dstack/_internal/core/backends/template/__init__.py +0 -0
  74. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  75. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  76. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  77. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  78. dstack/_internal/core/backends/tensordock/models.py +38 -0
  79. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  80. dstack/_internal/core/backends/vastai/backend.py +16 -0
  81. dstack/_internal/core/backends/vastai/compute.py +2 -2
  82. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  83. dstack/_internal/core/backends/vastai/models.py +37 -0
  84. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  85. dstack/_internal/core/backends/vultr/backend.py +16 -0
  86. dstack/_internal/core/backends/vultr/compute.py +10 -24
  87. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  88. dstack/_internal/core/backends/vultr/models.py +34 -0
  89. dstack/_internal/core/models/backends/__init__.py +0 -184
  90. dstack/_internal/core/models/backends/base.py +0 -19
  91. dstack/_internal/core/models/configurations.py +22 -16
  92. dstack/_internal/core/models/envs.py +4 -3
  93. dstack/_internal/core/models/fleets.py +17 -22
  94. dstack/_internal/core/models/gateways.py +3 -3
  95. dstack/_internal/core/models/instances.py +24 -0
  96. dstack/_internal/core/models/profiles.py +85 -45
  97. dstack/_internal/core/models/projects.py +1 -1
  98. dstack/_internal/core/models/repos/base.py +0 -5
  99. dstack/_internal/core/models/repos/local.py +3 -3
  100. dstack/_internal/core/models/repos/remote.py +26 -12
  101. dstack/_internal/core/models/repos/virtual.py +1 -1
  102. dstack/_internal/core/models/resources.py +45 -76
  103. dstack/_internal/core/models/runs.py +21 -19
  104. dstack/_internal/core/models/volumes.py +1 -3
  105. dstack/_internal/core/services/profiles.py +7 -16
  106. dstack/_internal/core/services/repos.py +0 -4
  107. dstack/_internal/server/app.py +11 -4
  108. dstack/_internal/server/background/__init__.py +10 -0
  109. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  110. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  111. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_placement_groups.py +5 -1
  113. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +135 -0
  114. dstack/_internal/server/background/tasks/process_running_jobs.py +80 -24
  115. dstack/_internal/server/background/tasks/process_runs.py +1 -0
  116. dstack/_internal/server/background/tasks/process_submitted_jobs.py +20 -38
  117. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  118. dstack/_internal/server/migrations/versions/60e444118b6d_add_jobprometheusmetrics.py +40 -0
  119. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  120. dstack/_internal/server/migrations/versions/98d1b92988bc_add_jobterminationreason_terminated_due_.py +140 -0
  121. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  122. dstack/_internal/server/models.py +59 -9
  123. dstack/_internal/server/routers/backends.py +14 -23
  124. dstack/_internal/server/routers/instances.py +3 -4
  125. dstack/_internal/server/routers/metrics.py +31 -10
  126. dstack/_internal/server/routers/prometheus.py +36 -0
  127. dstack/_internal/server/routers/repos.py +1 -2
  128. dstack/_internal/server/routers/runs.py +13 -59
  129. dstack/_internal/server/schemas/gateways.py +14 -23
  130. dstack/_internal/server/schemas/projects.py +7 -2
  131. dstack/_internal/server/schemas/repos.py +2 -38
  132. dstack/_internal/server/schemas/runner.py +1 -0
  133. dstack/_internal/server/schemas/runs.py +1 -24
  134. dstack/_internal/server/security/permissions.py +1 -1
  135. dstack/_internal/server/services/backends/__init__.py +85 -158
  136. dstack/_internal/server/services/config.py +53 -567
  137. dstack/_internal/server/services/fleets.py +9 -103
  138. dstack/_internal/server/services/gateways/__init__.py +13 -4
  139. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  140. dstack/_internal/server/services/jobs/__init__.py +9 -6
  141. dstack/_internal/server/services/jobs/configurators/base.py +25 -1
  142. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  143. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  144. dstack/_internal/server/services/metrics.py +131 -72
  145. dstack/_internal/server/services/offers.py +1 -1
  146. dstack/_internal/server/services/projects.py +23 -14
  147. dstack/_internal/server/services/prometheus.py +245 -0
  148. dstack/_internal/server/services/runner/client.py +14 -3
  149. dstack/_internal/server/services/runs.py +67 -31
  150. dstack/_internal/server/services/volumes.py +9 -4
  151. dstack/_internal/server/settings.py +3 -0
  152. dstack/_internal/server/statics/index.html +1 -1
  153. dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js → main-4a0fe83e84574654e397.js} +76 -19
  154. dstack/_internal/server/statics/{main-fe8fd9db55df8d10e648.js.map → main-4a0fe83e84574654e397.js.map} +1 -1
  155. dstack/_internal/server/statics/{main-7510e71dfa9749a4e70e.css → main-da9f8c06a69c20dac23e.css} +1 -1
  156. dstack/_internal/server/statics/static/media/entraID.d65d1f3e9486a8e56d24fc07b3230885.svg +9 -0
  157. dstack/_internal/server/testing/common.py +75 -32
  158. dstack/_internal/utils/json_schema.py +6 -0
  159. dstack/_internal/utils/ssh.py +2 -1
  160. dstack/api/__init__.py +4 -0
  161. dstack/api/_public/__init__.py +16 -20
  162. dstack/api/_public/backends.py +1 -1
  163. dstack/api/_public/repos.py +36 -36
  164. dstack/api/_public/runs.py +170 -83
  165. dstack/api/server/__init__.py +11 -13
  166. dstack/api/server/_backends.py +12 -16
  167. dstack/api/server/_fleets.py +15 -55
  168. dstack/api/server/_gateways.py +3 -14
  169. dstack/api/server/_repos.py +1 -4
  170. dstack/api/server/_runs.py +21 -96
  171. dstack/api/server/_volumes.py +10 -5
  172. dstack/api/utils.py +3 -0
  173. dstack/version.py +1 -1
  174. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/METADATA +10 -1
  175. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/RECORD +229 -206
  176. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  177. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  178. tests/_internal/core/backends/aws/test_resources.py +1 -1
  179. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  180. tests/_internal/core/backends/cudo/__init__.py +0 -0
  181. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  182. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  183. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  184. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  185. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  186. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  187. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  188. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  189. tests/_internal/core/backends/runpod/__init__.py +0 -0
  190. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  191. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  192. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  193. tests/_internal/core/backends/vastai/__init__.py +0 -0
  194. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  195. tests/_internal/core/backends/vultr/__init__.py +0 -0
  196. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  197. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  198. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  199. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  200. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  201. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +186 -0
  202. tests/_internal/server/background/tasks/test_process_running_jobs.py +123 -19
  203. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  204. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  205. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  206. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  207. tests/_internal/server/routers/test_backends.py +6 -764
  208. tests/_internal/server/routers/test_fleets.py +2 -26
  209. tests/_internal/server/routers/test_gateways.py +27 -3
  210. tests/_internal/server/routers/test_instances.py +0 -10
  211. tests/_internal/server/routers/test_metrics.py +42 -0
  212. tests/_internal/server/routers/test_projects.py +56 -0
  213. tests/_internal/server/routers/test_prometheus.py +333 -0
  214. tests/_internal/server/routers/test_repos.py +0 -15
  215. tests/_internal/server/routers/test_runs.py +83 -275
  216. tests/_internal/server/routers/test_volumes.py +2 -3
  217. tests/_internal/server/services/backends/__init__.py +0 -0
  218. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  219. tests/_internal/server/services/test_config.py +7 -4
  220. tests/_internal/server/services/test_fleets.py +1 -4
  221. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  222. tests/_internal/server/services/test_metrics.py +167 -0
  223. tests/_internal/server/services/test_repos.py +1 -14
  224. tests/_internal/server/services/test_runs.py +0 -4
  225. dstack/_internal/cli/commands/pool.py +0 -581
  226. dstack/_internal/cli/commands/run.py +0 -75
  227. dstack/_internal/core/backends/aws/config.py +0 -18
  228. dstack/_internal/core/backends/azure/config.py +0 -12
  229. dstack/_internal/core/backends/base/config.py +0 -5
  230. dstack/_internal/core/backends/cudo/config.py +0 -9
  231. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  232. dstack/_internal/core/backends/gcp/config.py +0 -22
  233. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  234. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  235. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  236. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  237. dstack/_internal/core/backends/nebius/compute.py +0 -220
  238. dstack/_internal/core/backends/nebius/config.py +0 -6
  239. dstack/_internal/core/backends/nebius/types.py +0 -37
  240. dstack/_internal/core/backends/oci/config.py +0 -6
  241. dstack/_internal/core/backends/runpod/config.py +0 -9
  242. dstack/_internal/core/backends/tensordock/config.py +0 -9
  243. dstack/_internal/core/backends/vastai/config.py +0 -6
  244. dstack/_internal/core/backends/vultr/config.py +0 -9
  245. dstack/_internal/core/models/backends/aws.py +0 -86
  246. dstack/_internal/core/models/backends/azure.py +0 -68
  247. dstack/_internal/core/models/backends/cudo.py +0 -43
  248. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  249. dstack/_internal/core/models/backends/gcp.py +0 -67
  250. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  251. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  252. dstack/_internal/core/models/backends/nebius.py +0 -54
  253. dstack/_internal/core/models/backends/runpod.py +0 -40
  254. dstack/_internal/core/models/backends/tensordock.py +0 -44
  255. dstack/_internal/core/models/backends/vastai.py +0 -43
  256. dstack/_internal/core/models/backends/vultr.py +0 -40
  257. dstack/_internal/core/models/pools.py +0 -43
  258. dstack/_internal/server/routers/pools.py +0 -142
  259. dstack/_internal/server/schemas/pools.py +0 -38
  260. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  261. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  262. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  263. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  264. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  265. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  266. dstack/_internal/server/services/backends/configurators/runpod.py +0 -97
  267. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  268. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  269. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  270. dstack/api/_public/pools.py +0 -41
  271. dstack/api/_public/resources.py +0 -105
  272. dstack/api/server/_pools.py +0 -63
  273. tests/_internal/server/routers/test_pools.py +0 -612
  274. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  275. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/LICENSE.md +0 -0
  276. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/WHEEL +0 -0
  277. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/entry_points.txt +0 -0
  278. {dstack-0.18.43.dist-info → dstack-0.19.0.dist-info}/top_level.txt +0 -0
@@ -5,7 +5,7 @@ from dstack._internal.cli.services.profile import (
5
5
  apply_profile_args,
6
6
  register_profile_args,
7
7
  )
8
- from dstack._internal.core.models.profiles import Profile, ProfileRetryPolicy, SpotPolicy
8
+ from dstack._internal.core.models.profiles import Profile, ProfileRetry, SpotPolicy
9
9
 
10
10
 
11
11
  class TestProfileArgs:
@@ -51,21 +51,21 @@ class TestProfileArgs:
51
51
  assert profile.dict() == modified.dict()
52
52
 
53
53
  def test_retry(self):
54
- profile = Profile(name="test")
55
- profile.retry_policy = ProfileRetryPolicy(retry=True)
54
+ profile = Profile(name="test", retry=None)
56
55
  modified, _ = apply_args(profile, ["--retry"])
56
+ profile.retry = True
57
57
  assert profile.dict() == modified.dict()
58
58
 
59
59
  def test_no_retry(self):
60
- profile = Profile(name="test", retry_policy=ProfileRetryPolicy(retry=True, duration=3600))
60
+ profile = Profile(name="test", retry=None)
61
61
  modified, _ = apply_args(profile, ["--no-retry"])
62
- profile.retry_policy.retry = False
62
+ profile.retry = False
63
63
  assert profile.dict() == modified.dict()
64
64
 
65
65
  def test_retry_duration(self):
66
66
  profile = Profile(name="test")
67
67
  modified, _ = apply_args(profile, ["--retry-duration", "1h"])
68
- profile.retry_policy = ProfileRetryPolicy(retry=True, duration=3600)
68
+ profile.retry = ProfileRetry(on_events=None, duration="1h")
69
69
  assert profile.dict() == modified.dict()
70
70
 
71
71
 
@@ -0,0 +1,35 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.aws.configurator import AWSConfigurator
6
+ from dstack._internal.core.backends.aws.models import AWSAccessKeyCreds, AWSBackendConfigWithCreds
7
+ from dstack._internal.core.errors import (
8
+ BackendAuthError,
9
+ BackendInvalidCredentialsError,
10
+ )
11
+
12
+
13
+ class TestAWSConfigurator:
14
+ def test_validate_config_valid(self):
15
+ config = AWSBackendConfigWithCreds(
16
+ creds=AWSAccessKeyCreds(access_key="valid", secret_key="valid"), regions=["us-west-1"]
17
+ )
18
+ with (
19
+ patch("dstack._internal.core.backends.aws.auth.authenticate"),
20
+ patch("dstack._internal.core.backends.aws.compute.get_vpc_id_subnet_id_or_error"),
21
+ ):
22
+ AWSConfigurator().validate_config(config, default_creds_enabled=True)
23
+
24
+ def test_validate_config_invalid_creds(self):
25
+ config = AWSBackendConfigWithCreds(
26
+ creds=AWSAccessKeyCreds(access_key="invalid", secret_key="invalid"),
27
+ regions=["us-west-1"],
28
+ )
29
+ with (
30
+ patch("dstack._internal.core.backends.aws.auth.authenticate") as authenticate_mock,
31
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
32
+ ):
33
+ authenticate_mock.side_effect = BackendAuthError()
34
+ AWSConfigurator().validate_config(config, default_creds_enabled=True)
35
+ assert exc_info.value.fields == [["creds", "access_key"], ["creds", "secret_key"]]
@@ -3,6 +3,7 @@ from unittest.mock import Mock
3
3
 
4
4
  import pytest
5
5
 
6
+ from dstack._internal.core.backends.aws.models import AWSOSImage, AWSOSImageConfig
6
7
  from dstack._internal.core.backends.aws.resources import (
7
8
  _is_valid_tag_key,
8
9
  _is_valid_tag_value,
@@ -10,7 +11,6 @@ from dstack._internal.core.backends.aws.resources import (
10
11
  validate_tags,
11
12
  )
12
13
  from dstack._internal.core.errors import BackendError, ComputeResourceNotFoundError
13
- from dstack._internal.core.models.backends.aws import AWSOSImage, AWSOSImageConfig
14
14
 
15
15
 
16
16
  class TestIsValidTagKey:
@@ -0,0 +1,61 @@
1
+ from unittest.mock import Mock, patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.azure.configurator import AzureConfigurator
6
+ from dstack._internal.core.backends.azure.models import (
7
+ AzureBackendConfigWithCreds,
8
+ AzureClientCreds,
9
+ )
10
+ from dstack._internal.core.errors import (
11
+ BackendAuthError,
12
+ BackendInvalidCredentialsError,
13
+ )
14
+
15
+
16
+ class TestAzureConfigurator:
17
+ def test_validate_config_valid(self):
18
+ config = AzureBackendConfigWithCreds(
19
+ creds=AzureClientCreds(
20
+ tenant_id="valid",
21
+ client_id="valid",
22
+ client_secret="valid",
23
+ ),
24
+ tenant_id="ten1",
25
+ subscription_id="sub1",
26
+ regions=["eastus"],
27
+ )
28
+ with (
29
+ patch("dstack._internal.core.backends.azure.auth.authenticate") as authenticate_mock,
30
+ patch("azure.mgmt.subscription.SubscriptionClient") as SubscriptionClientMock,
31
+ ):
32
+ authenticate_mock.return_value = Mock(), Mock()
33
+ subcription_client_mock = SubscriptionClientMock.return_value
34
+ subcription_client_mock.tenants.list.return_value = [Mock(tenant_id="ten1")]
35
+ subcription_client_mock.subscriptions.list.return_value = [
36
+ Mock(subscription_id="sub1")
37
+ ]
38
+ AzureConfigurator().validate_config(config, default_creds_enabled=True)
39
+
40
+ def test_validate_config_invalid_creds(self):
41
+ config = AzureBackendConfigWithCreds(
42
+ creds=AzureClientCreds(
43
+ tenant_id="invalid",
44
+ client_id="invalid",
45
+ client_secret="invalid",
46
+ ),
47
+ tenant_id="invalid",
48
+ subscription_id="invalid",
49
+ regions=["eastus"],
50
+ )
51
+ with (
52
+ patch("dstack._internal.core.backends.azure.auth.authenticate") as mock_authenticate,
53
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
54
+ ):
55
+ mock_authenticate.side_effect = BackendAuthError()
56
+ AzureConfigurator().validate_config(config, default_creds_enabled=True)
57
+ assert exc_info.value.fields == [
58
+ ["creds", "tenant_id"],
59
+ ["creds", "client_id"],
60
+ ["creds", "client_secret"],
61
+ ]
File without changes
@@ -0,0 +1,37 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.cudo.configurator import CudoConfigurator
6
+ from dstack._internal.core.backends.cudo.models import CudoBackendConfigWithCreds, CudoCreds
7
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
8
+
9
+
10
+ class TestCudoConfigurator:
11
+ def test_validate_config_valid(self):
12
+ config = CudoBackendConfigWithCreds(
13
+ creds=CudoCreds(api_key="valid"),
14
+ project_id="project1",
15
+ regions=["no-luster-1"],
16
+ )
17
+ with patch(
18
+ "dstack._internal.core.backends.cudo.api_client.CudoApiClient.validate_api_key"
19
+ ) as validate_mock:
20
+ validate_mock.return_value = True
21
+ CudoConfigurator().validate_config(config, default_creds_enabled=True)
22
+
23
+ def test_validate_config_invalid_creds(self):
24
+ config = CudoBackendConfigWithCreds(
25
+ creds=CudoCreds(api_key="invalid"),
26
+ project_id="project1",
27
+ regions=["no-luster-1"],
28
+ )
29
+ with (
30
+ patch(
31
+ "dstack._internal.core.backends.cudo.api_client.CudoApiClient.validate_api_key"
32
+ ) as validate_mock,
33
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
34
+ ):
35
+ validate_mock.return_value = False
36
+ CudoConfigurator().validate_config(config, default_creds_enabled=True)
37
+ assert exc_info.value.fields == [["creds", "api_key"]]
File without changes
@@ -0,0 +1,17 @@
1
+ from dstack._internal.core.backends.datacrunch.configurator import (
2
+ DataCrunchConfigurator,
3
+ )
4
+ from dstack._internal.core.backends.datacrunch.models import (
5
+ DataCrunchBackendConfigWithCreds,
6
+ DataCrunchCreds,
7
+ )
8
+
9
+
10
+ class TestDataCrunchConfigurator:
11
+ def test_validate_config_valid(self):
12
+ config = DataCrunchBackendConfigWithCreds(
13
+ creds=DataCrunchCreds(client_id="valid", client_secret="valid"),
14
+ regions=["FIN-01"],
15
+ )
16
+ # Currently no validation is implemented
17
+ DataCrunchConfigurator().validate_config(config, default_creds_enabled=True)
@@ -0,0 +1,42 @@
1
+ from unittest.mock import Mock, patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.gcp.configurator import GCPConfigurator
6
+ from dstack._internal.core.backends.gcp.models import (
7
+ GCPBackendConfigWithCreds,
8
+ GCPServiceAccountCreds,
9
+ )
10
+ from dstack._internal.core.errors import (
11
+ BackendAuthError,
12
+ BackendInvalidCredentialsError,
13
+ )
14
+
15
+
16
+ class TestGCPConfigurator:
17
+ def test_validate_config_valid(self):
18
+ config = GCPBackendConfigWithCreds(
19
+ creds=GCPServiceAccountCreds(data="valid", filename="-"),
20
+ project_id="valid-project",
21
+ regions=["us-west1"],
22
+ )
23
+ with (
24
+ patch("dstack._internal.core.backends.gcp.auth.authenticate") as authenticate_mock,
25
+ patch("dstack._internal.core.backends.gcp.resources.check_vpc"),
26
+ ):
27
+ authenticate_mock.return_value = Mock(), Mock()
28
+ GCPConfigurator().validate_config(config, default_creds_enabled=True)
29
+
30
+ def test_validate_config_invalid_creds(self):
31
+ config = GCPBackendConfigWithCreds(
32
+ creds=GCPServiceAccountCreds(data="invalid", filename="-"),
33
+ project_id="invalid-project",
34
+ regions=["us-west1"],
35
+ )
36
+ with (
37
+ patch("dstack._internal.core.backends.gcp.auth.authenticate") as authenticate_mock,
38
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
39
+ ):
40
+ authenticate_mock.side_effect = BackendAuthError()
41
+ GCPConfigurator().validate_config(config, default_creds_enabled=True)
42
+ assert exc_info.value.fields == [["creds", "data"]]
@@ -0,0 +1,43 @@
1
+ from unittest.mock import Mock, patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.kubernetes.configurator import (
6
+ KubernetesConfigurator,
7
+ )
8
+ from dstack._internal.core.backends.kubernetes.models import (
9
+ KubeconfigConfig,
10
+ KubernetesBackendConfigWithCreds,
11
+ KubernetesNetworkingConfig,
12
+ )
13
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
14
+
15
+
16
+ class TestKubernetesConfigurator:
17
+ def test_validate_config_valid(self):
18
+ config = KubernetesBackendConfigWithCreds(
19
+ kubeconfig=KubeconfigConfig(data="valid", filename="-"),
20
+ networking=KubernetesNetworkingConfig(ssh_host=None, ssh_port=None),
21
+ )
22
+ with patch(
23
+ "dstack._internal.core.backends.kubernetes.utils.get_api_from_config_data"
24
+ ) as get_api_mock:
25
+ api_mock = Mock()
26
+ api_mock.list_node.return_value = Mock()
27
+ get_api_mock.return_value = api_mock
28
+ KubernetesConfigurator().validate_config(config, default_creds_enabled=True)
29
+
30
+ def test_validate_config_invalid_config(self):
31
+ config = KubernetesBackendConfigWithCreds(
32
+ kubeconfig=KubeconfigConfig(data="invalid", filename="-"),
33
+ networking=KubernetesNetworkingConfig(ssh_host=None, ssh_port=None),
34
+ )
35
+ with (
36
+ patch(
37
+ "dstack._internal.core.backends.kubernetes.utils.get_api_from_config_data"
38
+ ) as get_api_mock,
39
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
40
+ ):
41
+ get_api_mock.side_effect = Exception("Invalid config")
42
+ KubernetesConfigurator().validate_config(config, default_creds_enabled=True)
43
+ assert exc_info.value.fields == [["kubeconfig"]]
File without changes
@@ -0,0 +1,38 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.lambdalabs.configurator import LambdaConfigurator
6
+ from dstack._internal.core.backends.lambdalabs.models import (
7
+ LambdaBackendConfigWithCreds,
8
+ LambdaCreds,
9
+ )
10
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
11
+
12
+
13
+ class TestLambdaConfigurator:
14
+ def test_validate_config_valid(self):
15
+ config = LambdaBackendConfigWithCreds(
16
+ creds=LambdaCreds(api_key="valid"),
17
+ regions=["us-east-1"],
18
+ )
19
+ with patch(
20
+ "dstack._internal.core.backends.lambdalabs.api_client.LambdaAPIClient.validate_api_key"
21
+ ) as validate_mock:
22
+ validate_mock.return_value = True
23
+ LambdaConfigurator().validate_config(config, default_creds_enabled=True)
24
+
25
+ def test_validate_config_invalid_creds(self):
26
+ config = LambdaBackendConfigWithCreds(
27
+ creds=LambdaCreds(api_key="invalid"),
28
+ regions=["us-east-1"],
29
+ )
30
+ with (
31
+ patch(
32
+ "dstack._internal.core.backends.lambdalabs.api_client.LambdaAPIClient.validate_api_key"
33
+ ) as validate_mock,
34
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
35
+ ):
36
+ validate_mock.return_value = False
37
+ LambdaConfigurator().validate_config(config, default_creds_enabled=True)
38
+ assert exc_info.value.fields == [["creds", "api_key"]]
@@ -0,0 +1,55 @@
1
+ from unittest.mock import Mock, patch
2
+
3
+ import pytest
4
+ from oci.exceptions import ClientError
5
+
6
+ from dstack._internal.core.backends.oci.configurator import OCIConfigurator
7
+ from dstack._internal.core.backends.oci.models import (
8
+ OCIBackendConfigWithCreds,
9
+ OCIClientCreds,
10
+ )
11
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
12
+
13
+
14
+ class TestOCIConfigurator:
15
+ def test_validate_config_valid(self):
16
+ config = OCIBackendConfigWithCreds(
17
+ creds=OCIClientCreds(
18
+ user="valid_user",
19
+ tenancy="valid_tenancy",
20
+ key_content="valid_key",
21
+ key_file=None,
22
+ pass_phrase=None,
23
+ fingerprint="valid_fingerprint",
24
+ region="us-ashburn-1",
25
+ ),
26
+ regions=["us-ashburn-1"],
27
+ )
28
+ with patch(
29
+ "dstack._internal.core.backends.oci.configurator.get_subscribed_regions"
30
+ ) as regions_mock:
31
+ regions_mock.return_value = Mock(names=["us-ashburn-1"])
32
+ OCIConfigurator().validate_config(config, default_creds_enabled=True)
33
+
34
+ def test_validate_config_invalid_creds(self):
35
+ config = OCIBackendConfigWithCreds(
36
+ creds=OCIClientCreds(
37
+ user="invalid_user",
38
+ tenancy="invalid_tenancy",
39
+ key_content="invalid_key",
40
+ key_file=None,
41
+ pass_phrase=None,
42
+ fingerprint="invalid_fingerprint",
43
+ region="us-ashburn-1",
44
+ ),
45
+ regions=["us-ashburn-1"],
46
+ )
47
+ with (
48
+ patch(
49
+ "dstack._internal.core.backends.oci.configurator.get_subscribed_regions"
50
+ ) as regions_mock,
51
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
52
+ ):
53
+ regions_mock.side_effect = ClientError("Invalid credentials")
54
+ OCIConfigurator().validate_config(config, default_creds_enabled=True)
55
+ assert exc_info.value.fields == [["creds"]]
File without changes
@@ -0,0 +1,33 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.runpod.configurator import RunpodConfigurator
6
+ from dstack._internal.core.backends.runpod.models import RunpodBackendConfigWithCreds, RunpodCreds
7
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
8
+
9
+
10
+ class TestRunpodConfigurator:
11
+ def test_validate_config_valid(self):
12
+ config = RunpodBackendConfigWithCreds(
13
+ creds=RunpodCreds(api_key="valid"),
14
+ )
15
+ with patch(
16
+ "dstack._internal.core.backends.runpod.api_client.RunpodApiClient.validate_api_key"
17
+ ) as validate_mock:
18
+ validate_mock.return_value = True
19
+ RunpodConfigurator().validate_config(config, default_creds_enabled=True)
20
+
21
+ def test_validate_config_invalid_creds(self):
22
+ config = RunpodBackendConfigWithCreds(
23
+ creds=RunpodCreds(api_key="invalid"),
24
+ )
25
+ with (
26
+ patch(
27
+ "dstack._internal.core.backends.runpod.api_client.RunpodApiClient.validate_api_key"
28
+ ) as validate_mock,
29
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
30
+ ):
31
+ validate_mock.return_value = False
32
+ RunpodConfigurator().validate_config(config, default_creds_enabled=True)
33
+ assert exc_info.value.fields == [["creds", "api_key"]]
File without changes
@@ -0,0 +1,38 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.tensordock.configurator import (
6
+ TensorDockConfigurator,
7
+ )
8
+ from dstack._internal.core.backends.tensordock.models import (
9
+ TensorDockBackendConfigWithCreds,
10
+ TensorDockCreds,
11
+ )
12
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
13
+
14
+
15
+ class TestTensorDockConfigurator:
16
+ def test_validate_config_valid(self):
17
+ config = TensorDockBackendConfigWithCreds(
18
+ creds=TensorDockCreds(api_key="valid", api_token="valid"),
19
+ )
20
+ with patch(
21
+ "dstack._internal.core.backends.tensordock.api_client.TensorDockAPIClient.auth_test"
22
+ ) as auth_test_mock:
23
+ auth_test_mock.return_value = True
24
+ TensorDockConfigurator().validate_config(config, default_creds_enabled=True)
25
+
26
+ def test_validate_config_invalid_creds(self):
27
+ config = TensorDockBackendConfigWithCreds(
28
+ creds=TensorDockCreds(api_key="invalid", api_token="invalid"),
29
+ )
30
+ with (
31
+ patch(
32
+ "dstack._internal.core.backends.tensordock.api_client.TensorDockAPIClient.auth_test"
33
+ ) as auth_test_mock,
34
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
35
+ ):
36
+ auth_test_mock.return_value = False
37
+ TensorDockConfigurator().validate_config(config, default_creds_enabled=True)
38
+ assert exc_info.value.fields == [["creds", "api_key"], ["creds", "api_token"]]
File without changes
@@ -0,0 +1,33 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.vastai.configurator import VastAIConfigurator
6
+ from dstack._internal.core.backends.vastai.models import VastAIBackendConfigWithCreds, VastAICreds
7
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
8
+
9
+
10
+ class TestVastAIConfigurator:
11
+ def test_validate_config_valid(self):
12
+ config = VastAIBackendConfigWithCreds(
13
+ creds=VastAICreds(api_key="valid"),
14
+ )
15
+ with patch(
16
+ "dstack._internal.core.backends.vastai.api_client.VastAIAPIClient.auth_test"
17
+ ) as auth_test_mock:
18
+ auth_test_mock.return_value = True
19
+ VastAIConfigurator().validate_config(config, default_creds_enabled=True)
20
+
21
+ def test_validate_config_invalid_creds(self):
22
+ config = VastAIBackendConfigWithCreds(
23
+ creds=VastAICreds(api_key="invalid"),
24
+ )
25
+ with (
26
+ patch(
27
+ "dstack._internal.core.backends.vastai.api_client.VastAIAPIClient.auth_test"
28
+ ) as auth_test_mock,
29
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
30
+ ):
31
+ auth_test_mock.return_value = False
32
+ VastAIConfigurator().validate_config(config, default_creds_enabled=True)
33
+ assert exc_info.value.fields == [["creds", "api_key"]]
File without changes
@@ -0,0 +1,33 @@
1
+ from unittest.mock import patch
2
+
3
+ import pytest
4
+
5
+ from dstack._internal.core.backends.vultr.configurator import VultrConfigurator
6
+ from dstack._internal.core.backends.vultr.models import VultrBackendConfigWithCreds, VultrCreds
7
+ from dstack._internal.core.errors import BackendInvalidCredentialsError
8
+
9
+
10
+ class TestVultrConfigurator:
11
+ def test_validate_config_valid(self):
12
+ config = VultrBackendConfigWithCreds(
13
+ creds=VultrCreds(api_key="valid"),
14
+ )
15
+ with patch(
16
+ "dstack._internal.core.backends.vultr.api_client.VultrApiClient.validate_api_key"
17
+ ) as validate_mock:
18
+ validate_mock.return_value = True
19
+ VultrConfigurator().validate_config(config, default_creds_enabled=True)
20
+
21
+ def test_validate_config_invalid_creds(self):
22
+ config = VultrBackendConfigWithCreds(
23
+ creds=VultrCreds(api_key="invalid"),
24
+ )
25
+ with (
26
+ patch(
27
+ "dstack._internal.core.backends.vultr.api_client.VultrApiClient.validate_api_key"
28
+ ) as validate_mock,
29
+ pytest.raises(BackendInvalidCredentialsError) as exc_info,
30
+ ):
31
+ validate_mock.return_value = False
32
+ VultrConfigurator().validate_config(config, default_creds_enabled=True)
33
+ assert exc_info.value.fields == [["creds", "api_key"]]
@@ -8,6 +8,7 @@ from dstack._internal.core.models.gateways import GatewayProvisioningData, Gatew
8
8
  from dstack._internal.server.background.tasks.process_gateways import process_submitted_gateways
9
9
  from dstack._internal.server.testing.common import (
10
10
  AsyncContextManager,
11
+ ComputeMockSpec,
11
12
  create_backend,
12
13
  create_gateway,
13
14
  create_project,
@@ -37,6 +38,7 @@ class TestProcessSubmittedGateways:
37
38
  m.return_value = (backend, aws)
38
39
  pool_add.return_value = MagicMock()
39
40
  pool_add.return_value.client.return_value = MagicMock(AsyncContextManager())
41
+ aws.compute.return_value = Mock(spec=ComputeMockSpec)
40
42
  aws.compute.return_value.create_gateway.return_value = GatewayProvisioningData(
41
43
  instance_id="i-1234567890",
42
44
  ip_address="2.2.2.2",
@@ -68,6 +70,7 @@ class TestProcessSubmittedGateways:
68
70
  ) as m:
69
71
  aws = Mock()
70
72
  m.return_value = (backend, aws)
73
+ aws.compute.return_value = Mock(spec=ComputeMockSpec)
71
74
  aws.compute.return_value.create_gateway.side_effect = BackendError("Some error")
72
75
  await process_submitted_gateways()
73
76
  m.assert_called_once()
@@ -99,6 +102,7 @@ class TestProcessSubmittedGateways:
99
102
  aws = Mock()
100
103
  m.return_value = (backend, aws)
101
104
  connect_to_gateway_with_retry_mock.return_value = None
105
+ aws.compute.return_value = Mock(spec=ComputeMockSpec)
102
106
  aws.compute.return_value.create_gateway.return_value = GatewayProvisioningData(
103
107
  instance_id="i-1234567890",
104
108
  ip_address="2.2.2.2",