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
@@ -1,581 +0,0 @@
1
- import argparse
2
- import getpass
3
- import ipaddress
4
- import time
5
- import urllib.parse
6
- from pathlib import Path
7
- from typing import Optional, Sequence, Tuple
8
-
9
- from rich.console import Group
10
- from rich.live import Live
11
- from rich.table import Table
12
-
13
- from dstack._internal.cli.commands import APIBaseCommand
14
- from dstack._internal.cli.services.args import cpu_spec, disk_spec, gpu_spec, memory_spec
15
- from dstack._internal.cli.services.profile import (
16
- apply_profile_args,
17
- register_profile_args,
18
- )
19
- from dstack._internal.cli.utils.common import confirm_ask, console
20
- from dstack._internal.core.errors import CLIError, ServerClientError
21
- from dstack._internal.core.models.instances import (
22
- InstanceAvailability,
23
- InstanceOfferWithAvailability,
24
- InstanceStatus,
25
- SSHKey,
26
- )
27
- from dstack._internal.core.models.pools import Instance, Pool
28
- from dstack._internal.core.models.profiles import Profile, SpotPolicy, parse_duration
29
- from dstack._internal.core.models.resources import DEFAULT_CPU_COUNT, DEFAULT_MEMORY_SIZE
30
- from dstack._internal.core.models.runs import Requirements, get_policy_map
31
- from dstack._internal.utils.common import pretty_date
32
- from dstack._internal.utils.logging import get_logger
33
- from dstack._internal.utils.ssh import convert_ssh_key_to_pem, generate_public_key, pkey_from_str
34
- from dstack.api._public.resources import Resources
35
- from dstack.api.utils import load_profile
36
-
37
- REFRESH_RATE_PER_SEC = 5
38
- LIVE_PROVISION_INTERVAL_SECS = 10
39
-
40
- logger = get_logger(__name__)
41
-
42
-
43
- class PoolCommand(APIBaseCommand):
44
- NAME = "pool"
45
- DESCRIPTION = "Manage pools"
46
-
47
- def _register(self) -> None:
48
- super()._register()
49
- self._parser.set_defaults(subfunc=self._list)
50
-
51
- subparsers = self._parser.add_subparsers(dest="action")
52
-
53
- # list pools
54
- list_parser = subparsers.add_parser(
55
- "list",
56
- help="List pools",
57
- description="List available pools",
58
- formatter_class=self._parser.formatter_class,
59
- )
60
- list_parser.add_argument("-v", "--verbose", help="Show more information")
61
- list_parser.set_defaults(subfunc=self._list)
62
-
63
- # create pool
64
- create_parser = subparsers.add_parser(
65
- "create", help="Create pool", formatter_class=self._parser.formatter_class
66
- )
67
- create_parser.add_argument(
68
- "-n", "--name", dest="pool_name", help="The name of the pool", required=True
69
- )
70
- create_parser.set_defaults(subfunc=self._create)
71
-
72
- # delete pool
73
- delete_parser = subparsers.add_parser(
74
- "delete", help="Delete pool", formatter_class=self._parser.formatter_class
75
- )
76
- delete_parser.add_argument(
77
- "-n", "--name", dest="pool_name", help="The name of the pool", required=True
78
- )
79
- # TODO: support --force
80
- delete_parser.set_defaults(subfunc=self._delete)
81
-
82
- # show pool instances
83
- ps_parser = subparsers.add_parser(
84
- "ps",
85
- help="Show pool instances",
86
- description="Show instances in the pool",
87
- formatter_class=self._parser.formatter_class,
88
- )
89
- ps_parser.add_argument(
90
- "--pool",
91
- dest="pool_name",
92
- help="The name of the pool. If not set, the default pool will be used",
93
- )
94
- ps_parser.add_argument(
95
- "-w",
96
- "--watch",
97
- help="Watch instances in realtime",
98
- action="store_true",
99
- )
100
- ps_parser.set_defaults(subfunc=self._ps)
101
-
102
- # add instance
103
- add_parser = subparsers.add_parser(
104
- "add", help="Add instance to pool", formatter_class=self._parser.formatter_class
105
- )
106
- self._parser.add_argument(
107
- "--max-offers",
108
- help="Number of offers to show in the run plan",
109
- type=int,
110
- default=3,
111
- )
112
- add_parser.add_argument(
113
- "-y", "--yes", help="Don't ask for confirmation", action="store_true"
114
- )
115
- register_profile_args(add_parser, pool_add=True)
116
- register_resource_args(add_parser)
117
- add_parser.set_defaults(subfunc=self._add)
118
-
119
- # remove instance
120
- remove_parser = subparsers.add_parser(
121
- "rm",
122
- help="Remove instance from the pool",
123
- formatter_class=self._parser.formatter_class,
124
- aliases=["remove"],
125
- )
126
- remove_parser.add_argument(
127
- "instance_name",
128
- help="The name of the instance",
129
- )
130
- remove_parser.add_argument(
131
- "--pool",
132
- dest="pool_name",
133
- help="The name of the pool. If not set, the default pool will be used",
134
- )
135
- remove_parser.add_argument(
136
- "--force",
137
- action="store_true",
138
- help="The name of the instance",
139
- )
140
- remove_parser.add_argument(
141
- "-y", "--yes", help="Don't ask for confirmation", action="store_true"
142
- )
143
- remove_parser.set_defaults(subfunc=self._remove)
144
-
145
- # pool set-default
146
- set_default_parser = subparsers.add_parser(
147
- "set-default",
148
- help="Set the project's default pool",
149
- formatter_class=self._parser.formatter_class,
150
- )
151
- set_default_parser.add_argument(
152
- "--pool", dest="pool_name", help="The name of the pool", required=True
153
- )
154
- set_default_parser.set_defaults(subfunc=self._set_default)
155
-
156
- # add-ssh
157
- add_ssh = subparsers.add_parser(
158
- "add-ssh",
159
- help="Add remote instance to pool",
160
- formatter_class=self._parser.formatter_class,
161
- )
162
- add_ssh.add_argument("destination")
163
- add_ssh.add_argument(
164
- "-i",
165
- metavar="SSH_PRIVATE_KEY",
166
- help="The private SSH key path for SSH",
167
- type=Path,
168
- dest="ssh_identity_file",
169
- required=True,
170
- )
171
- add_ssh.add_argument("-p", help="SSH port to connect", dest="ssh_port", type=int)
172
- add_ssh.add_argument("-l", help="User to login", dest="login_name")
173
- add_ssh.add_argument("--region", help="Host region", dest="region")
174
- add_ssh.add_argument("--pool", help="Pool name", dest="pool_name")
175
- add_ssh.add_argument("--name", dest="instance_name", help="Set the name of the instance")
176
- add_ssh.add_argument(
177
- "--network",
178
- dest="network",
179
- help="Network address for multinode setup. Format <ip address>/<netmask>",
180
- )
181
- add_ssh.set_defaults(subfunc=self._add_ssh)
182
-
183
- def _list(self, args: argparse.Namespace) -> None:
184
- pools = self.api.client.pool.list(self.api.project)
185
- print_pool_table(pools, verbose=getattr(args, "verbose", False))
186
-
187
- def _create(self, args: argparse.Namespace) -> None:
188
- self.api.client.pool.create(self.api.project, args.pool_name)
189
- console.print(f"Pool {args.pool_name!r} created")
190
-
191
- def _delete(self, args: argparse.Namespace) -> None:
192
- # TODO(egor-s): ask for confirmation
193
- with console.status("Removing pool..."):
194
- self.api.client.pool.delete(self.api.project, args.pool_name, False)
195
- console.print(f"Pool {args.pool_name!r} removed")
196
-
197
- def _remove(self, args: argparse.Namespace) -> None:
198
- pool = self.api.client.pool.show(self.api.project, args.pool_name)
199
- pool.instances = [i for i in pool.instances if i.name == args.instance_name]
200
- if not pool.instances:
201
- raise CLIError(f"Instance {args.instance_name!r} not found in pool {pool.name!r}")
202
-
203
- console.print(f" [bold]Pool name[/] {pool.name}\n")
204
- print_instance_table(pool.instances)
205
-
206
- if not args.force and any(i.status == InstanceStatus.BUSY for i in pool.instances):
207
- # TODO(egor-s): implement this logic in the server too
208
- raise CLIError("Can't remove busy instance. Use `--force` to remove anyway")
209
-
210
- if not args.yes and not confirm_ask(f"Remove instance {args.instance_name!r}?"):
211
- console.print("\nExiting...")
212
- return
213
-
214
- with console.status("Removing instance..."):
215
- self.api.client.pool.remove(
216
- self.api.project, pool.name, args.instance_name, args.force
217
- )
218
- console.print(f"Instance {args.instance_name!r} removed")
219
-
220
- def _set_default(self, args: argparse.Namespace) -> None:
221
- self.api.client.pool.set_default(self.api.project, args.pool_name)
222
-
223
- def _ps(self, args: argparse.Namespace) -> None:
224
- pool_name_template = " [bold]Pool name[/] {}\n"
225
- if not args.watch:
226
- resp = self.api.client.pool.show(self.api.project, args.pool_name)
227
- console.print(pool_name_template.format(resp.name))
228
- print_instance_table(resp.instances)
229
- return
230
-
231
- try:
232
- with Live(console=console, refresh_per_second=REFRESH_RATE_PER_SEC) as live:
233
- while True:
234
- resp = self.api.client.pool.show(self.api.project, args.pool_name)
235
- group = Group(
236
- pool_name_template.format(resp.name), get_instance_table(resp.instances)
237
- )
238
- live.update(group)
239
- time.sleep(LIVE_PROVISION_INTERVAL_SECS)
240
- except KeyboardInterrupt:
241
- pass
242
-
243
- def _add(self, args: argparse.Namespace) -> None:
244
- super()._command(args)
245
-
246
- resources = Resources(
247
- cpu=args.cpu,
248
- memory=args.memory,
249
- gpu=args.gpu,
250
- shm_size=args.shared_memory,
251
- disk=args.disk,
252
- )
253
-
254
- profile = load_profile(Path.cwd(), args.profile)
255
- apply_profile_args(args, profile, pool_add=True)
256
-
257
- spot = get_policy_map(profile.spot_policy, default=SpotPolicy.ONDEMAND)
258
-
259
- requirements = Requirements(
260
- resources=resources,
261
- max_price=profile.max_price,
262
- spot=spot,
263
- )
264
-
265
- with console.status("Getting instances..."):
266
- pool_offers = self.api.runs.get_offers(profile, requirements)
267
-
268
- profile.pool_name = pool_offers.pool_name
269
-
270
- print_offers_table(
271
- profile=profile,
272
- requirements=requirements,
273
- instance_offers=pool_offers.instances,
274
- offers_limit=args.max_offers,
275
- )
276
- if not pool_offers.instances:
277
- console.print("\nThere are no offers with these criteria. Exiting...")
278
- return
279
-
280
- if not args.yes and not confirm_ask("Continue?"):
281
- console.print("\nExiting...")
282
- return
283
-
284
- try:
285
- with console.status("Creating instance..."):
286
- # TODO: Instance name is not passed, so --instance does not work.
287
- # There is profile.instance_name but it makes sense for `dstack run` only.
288
- instance = self.api.runs.create_instance(profile, requirements)
289
- except ServerClientError as e:
290
- raise CLIError(e.msg)
291
- console.print()
292
- print_instance_table([instance])
293
-
294
- def _add_ssh(self, args: argparse.Namespace) -> None:
295
- super()._command(args)
296
-
297
- # validate network
298
- if args.network is not None:
299
- try:
300
- network = ipaddress.IPv4Interface(args.network).network
301
- except ValueError as e:
302
- console.print(
303
- f"[error]Can't parse network. The address must be in the format <network address>/<netmask>, example `10.0.0.0/24`. Error: {e}[/]"
304
- )
305
- return
306
- if not network.is_private:
307
- console.print(
308
- f"[error]The network must be private network. The {network} is not private[/]"
309
- )
310
- return
311
-
312
- ssh_keys = []
313
- if args.ssh_identity_file:
314
- try:
315
- private_key = convert_ssh_key_to_pem(args.ssh_identity_file.read_text())
316
- try:
317
- pub_key = args.ssh_identity_file.with_suffix(".pub").read_text()
318
- except FileNotFoundError:
319
- pub_key = generate_public_key(pkey_from_str(private_key))
320
- ssh_key = SSHKey(public=pub_key, private=private_key)
321
- ssh_keys.append(ssh_key)
322
- except OSError:
323
- console.print("[error]Unable to read the public key.[/]")
324
- return
325
- except ValueError:
326
- console.print("[error]Key type is not supported.[/]")
327
- return
328
-
329
- login, ssh_host, port = parse_destination(args.destination)
330
-
331
- ssh_port = 22
332
- if port is not None:
333
- ssh_port = port
334
- if args.ssh_port is not None:
335
- ssh_port = args.ssh_port
336
-
337
- ssh_user = args.login_name
338
- if ssh_user is None:
339
- ssh_user = login
340
- if ssh_user is None:
341
- try:
342
- ssh_user = getpass.getuser()
343
- except OSError:
344
- console.print("[error]Set the user name with the `-l` parameter.[/]")
345
- return
346
-
347
- result = self.api.client.pool.add_remote(
348
- project_name=self.api.project,
349
- pool_name=args.pool_name,
350
- instance_name=args.instance_name,
351
- instance_network=args.network,
352
- region=args.region,
353
- host=ssh_host,
354
- port=ssh_port,
355
- ssh_user=ssh_user,
356
- ssh_keys=ssh_keys,
357
- )
358
- if not result:
359
- console.print(f"[error]Failed to add remote instance {args.instance_name!r}[/]")
360
- return
361
- console.print(
362
- f"Remote instance [code]{result.name!r}[/] has been added with status [secondary]{result.status.upper()}[/]"
363
- )
364
-
365
- def _command(self, args: argparse.Namespace) -> None:
366
- super()._command(args)
367
- logger.warning(
368
- "Pools are deprecated in favor of fleets and will be removed in 0.19.0. "
369
- "Learn more about fleets at https://dstack.ai/docs/concepts/fleets/"
370
- )
371
- # TODO handle 404 and other errors
372
- args.subfunc(args)
373
-
374
-
375
- def print_pool_table(pools: Sequence[Pool], verbose: bool) -> None:
376
- table = Table(box=None)
377
- table.add_column("NAME")
378
- table.add_column("DEFAULT")
379
- table.add_column("INSTANCES")
380
- if verbose:
381
- table.add_column("CREATED")
382
-
383
- sorted_pools = sorted(pools, key=lambda r: r.name)
384
- for pool in sorted_pools:
385
- default_mark = "default" if pool.default else ""
386
- style = "success" if pool.total_instances == pool.available_instances else "error"
387
- health = f"[{style}]{pool.available_instances}/{pool.total_instances}[/]"
388
- row = [pool.name, default_mark, health]
389
- if verbose:
390
- row.append(pretty_date(pool.created_at))
391
- table.add_row(*row)
392
-
393
- console.print(table)
394
- console.print()
395
-
396
-
397
- def print_instance_table(instances: Sequence[Instance]) -> None:
398
- console.print(get_instance_table(instances))
399
- console.print()
400
-
401
-
402
- def get_instance_table(instances: Sequence[Instance]) -> Table:
403
- table = Table(box=None)
404
- table.add_column("INSTANCE", no_wrap=True)
405
- table.add_column("BACKEND")
406
- table.add_column("REGION")
407
- table.add_column("RESOURCES")
408
- table.add_column("SPOT")
409
- table.add_column("PRICE")
410
- table.add_column("STATUS")
411
- table.add_column("CREATED")
412
-
413
- for instance in instances:
414
- resources = ""
415
- spot = ""
416
- if instance.instance_type is not None:
417
- resources = instance.instance_type.resources.pretty_format()
418
- spot = "yes" if instance.instance_type.resources.spot else "no"
419
-
420
- status = instance.status.value
421
- if instance.status in [InstanceStatus.IDLE, InstanceStatus.BUSY] and instance.unreachable:
422
- status += "\n(unreachable)"
423
-
424
- row = [
425
- instance.name,
426
- (instance.backend or "").replace("remote", "ssh"),
427
- instance.region or "",
428
- resources,
429
- spot,
430
- f"${instance.price:.4}" if instance.price is not None else "",
431
- status,
432
- pretty_date(instance.created),
433
- ]
434
- table.add_row(*row)
435
-
436
- return table
437
-
438
-
439
- def print_offers_table(
440
- profile: Profile,
441
- requirements: Requirements,
442
- instance_offers: Sequence[InstanceOfferWithAvailability],
443
- offers_limit: int,
444
- ) -> None:
445
- pretty_req = requirements.pretty_format(resources_only=True)
446
- max_price = f"${requirements.max_price:g}" if requirements.max_price else "-"
447
- termination_policy = profile.termination_policy
448
- termination_idle_time = f"{parse_duration(profile.termination_idle_time)}s"
449
-
450
- if requirements.spot is None:
451
- spot_policy = "auto"
452
- elif requirements.spot:
453
- spot_policy = "spot"
454
- else:
455
- spot_policy = "on-demand"
456
-
457
- def th(s: str) -> str:
458
- return f"[bold]{s}[/bold]"
459
-
460
- props = Table(box=None, show_header=False)
461
- props.add_column(no_wrap=True) # key
462
- props.add_column() # value
463
-
464
- props.add_row(th("Pool"), profile.pool_name)
465
- props.add_row(th("Min resources"), pretty_req)
466
- props.add_row(th("Max price"), max_price)
467
- props.add_row(th("Spot policy"), spot_policy)
468
- props.add_row(th("Termination policy"), termination_policy)
469
- props.add_row(th("Termination idle time"), termination_idle_time)
470
-
471
- offers_table = Table(box=None)
472
- offers_table.add_column("#")
473
- offers_table.add_column("BACKEND")
474
- offers_table.add_column("REGION")
475
- offers_table.add_column("INSTANCE")
476
- offers_table.add_column("RESOURCES")
477
- offers_table.add_column("SPOT")
478
- offers_table.add_column("PRICE")
479
- offers_table.add_column()
480
-
481
- print_offers = instance_offers[:offers_limit]
482
-
483
- for index, offer in enumerate(print_offers, start=1):
484
- resources = offer.instance.resources
485
-
486
- availability = ""
487
- if offer.availability in {
488
- InstanceAvailability.NOT_AVAILABLE,
489
- InstanceAvailability.NO_QUOTA,
490
- }:
491
- availability = offer.availability.value.replace("_", " ").title()
492
- offers_table.add_row(
493
- f"{index}",
494
- offer.backend.replace("remote", "ssh"),
495
- offer.region,
496
- offer.instance.name,
497
- resources.pretty_format(),
498
- "yes" if resources.spot else "no",
499
- f"${offer.price:g}",
500
- availability,
501
- style=None if index == 1 else "secondary",
502
- )
503
- if len(print_offers) > offers_limit:
504
- offers_table.add_row("", "...", style="secondary")
505
-
506
- console.print(props)
507
- console.print()
508
- if len(print_offers) > 0:
509
- console.print(offers_table)
510
- console.print()
511
-
512
-
513
- def register_resource_args(parser: argparse.ArgumentParser) -> None:
514
- resources_group = parser.add_argument_group("Resources")
515
- resources_group.add_argument(
516
- "--cpu",
517
- help=f"Request the CPU count. Default: {DEFAULT_CPU_COUNT}",
518
- dest="cpu",
519
- metavar="SPEC",
520
- default=DEFAULT_CPU_COUNT,
521
- type=cpu_spec,
522
- )
523
-
524
- resources_group.add_argument(
525
- "--memory",
526
- help="Request the size of RAM. "
527
- f"The format is [code]SIZE[/]:[code]MB|GB|TB[/]. Default: {DEFAULT_MEMORY_SIZE}",
528
- dest="memory",
529
- metavar="SIZE",
530
- default=DEFAULT_MEMORY_SIZE,
531
- type=memory_spec,
532
- )
533
-
534
- resources_group.add_argument(
535
- "--shared-memory",
536
- help="Request the size of Shared Memory. The format is [code]SIZE[/]:[code]MB|GB|TB[/].",
537
- dest="shared_memory",
538
- default=None,
539
- metavar="SIZE",
540
- )
541
-
542
- resources_group.add_argument(
543
- "--gpu",
544
- help="Request GPU for the run. "
545
- "The format is [code]NAME[/]:[code]COUNT[/]:[code]MEMORY[/] (all parts are optional)",
546
- dest="gpu",
547
- default=None,
548
- metavar="SPEC",
549
- type=gpu_spec,
550
- )
551
-
552
- resources_group.add_argument(
553
- "--disk",
554
- help="Request the size of disk for the run. Example [code]--disk 100GB..[/].",
555
- dest="disk",
556
- metavar="SIZE",
557
- default=None,
558
- type=disk_spec,
559
- )
560
-
561
-
562
- def parse_destination(destination: str) -> Tuple[Optional[str], str, Optional[int]]:
563
- port = None
564
- netloc = destination
565
-
566
- if destination.startswith("ssh://"):
567
- parse_result = urllib.parse.urlparse(destination)
568
- netloc, _, netloc_port = parse_result.netloc.partition(":")
569
- try:
570
- port = int(netloc_port)
571
- except ValueError:
572
- pass
573
-
574
- head, sep, tail = netloc.partition("@")
575
- if sep == "@":
576
- login = head
577
- host = tail
578
- else:
579
- login = None
580
- host = head
581
- return login, host, port
@@ -1,75 +0,0 @@
1
- import argparse
2
- from pathlib import Path
3
-
4
- from dstack._internal.cli.commands import APIBaseCommand
5
- from dstack._internal.cli.services.configurators import get_run_configurator_class
6
- from dstack._internal.cli.services.configurators.run import BaseRunConfigurator
7
- from dstack._internal.core.models.configurations import RunConfigurationType
8
- from dstack._internal.utils.logging import get_logger
9
- from dstack.api.utils import load_configuration
10
-
11
- logger = get_logger(__name__)
12
- NOTSET = object()
13
-
14
-
15
- class RunCommand(APIBaseCommand):
16
- NAME = "run"
17
- DESCRIPTION = "Run a configuration"
18
- DEFAULT_HELP = False
19
-
20
- def _register(self):
21
- super()._register()
22
- self._parser.add_argument(
23
- "-h",
24
- "--help",
25
- nargs="?",
26
- type=RunConfigurationType,
27
- default=NOTSET,
28
- help="Show this help message and exit. TYPE is one of [code]task[/], [code]dev-environment[/], [code]service[/]",
29
- dest="help",
30
- metavar="TYPE",
31
- )
32
- self._parser.add_argument("working_dir")
33
- self._parser.add_argument(
34
- "-f",
35
- "--file",
36
- type=Path,
37
- metavar="FILE",
38
- help="The path to the configuration file. Defaults to [code]$PWD/.dstack.yml[/]",
39
- dest="configuration_file",
40
- )
41
- self._parser.add_argument(
42
- "-y",
43
- "--yes",
44
- help="Do not ask for confirmation",
45
- action="store_true",
46
- )
47
-
48
- def _command(self, args: argparse.Namespace):
49
- if args.help is not NOTSET:
50
- if args.help is not None:
51
- configurator_class = get_run_configurator_class(RunConfigurationType(args.help))
52
- else:
53
- configurator_class = BaseRunConfigurator
54
- configurator_class.register_args(self._parser)
55
- self._parser.print_help()
56
- return
57
-
58
- super()._command(args)
59
-
60
- logger.warning("[code]dstack run[/] is deprecated in favor of [code]dstack apply[/].")
61
-
62
- configuration_path, configuration = load_configuration(
63
- Path.cwd(), configuration_file=args.configuration_file
64
- )
65
- configurator_class = get_run_configurator_class(configuration.type)
66
- configurator = configurator_class(api_client=self.api)
67
- configurator_parser = configurator.get_parser()
68
- known, unknown = configurator_parser.parse_known_args(args.unknown)
69
- configurator.apply_configuration(
70
- conf=configuration,
71
- configuration_path=configuration_path,
72
- command_args=args,
73
- configurator_args=known,
74
- unknown_args=unknown,
75
- )
@@ -1,18 +0,0 @@
1
- from dstack._internal.core.backends.base.config import BackendConfig
2
- from dstack._internal.core.models.backends.aws import AnyAWSCreds, AWSStoredConfig
3
-
4
-
5
- class AWSConfig(AWSStoredConfig, BackendConfig):
6
- creds: AnyAWSCreds
7
-
8
- @property
9
- def allocate_public_ips(self) -> bool:
10
- if self.public_ips is not None:
11
- return self.public_ips
12
- return True
13
-
14
- @property
15
- def use_default_vpcs(self) -> bool:
16
- if self.default_vpcs is not None:
17
- return self.default_vpcs
18
- return True
@@ -1,12 +0,0 @@
1
- from dstack._internal.core.backends.base.config import BackendConfig
2
- from dstack._internal.core.models.backends.azure import AnyAzureCreds, AzureStoredConfig
3
-
4
-
5
- class AzureConfig(AzureStoredConfig, BackendConfig):
6
- creds: AnyAzureCreds
7
-
8
- @property
9
- def allocate_public_ips(self) -> bool:
10
- if self.public_ips is not None:
11
- return self.public_ips
12
- return True
@@ -1,5 +0,0 @@
1
- from pydantic import BaseModel
2
-
3
-
4
- class BackendConfig(BaseModel):
5
- pass