dstack 0.19.6rc1__py3-none-any.whl → 0.19.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dstack might be problematic. Click here for more details.
- dstack/_internal/cli/services/configurators/fleet.py +3 -2
- dstack/_internal/cli/services/configurators/run.py +12 -2
- dstack/_internal/cli/utils/fleet.py +3 -1
- dstack/_internal/cli/utils/run.py +25 -28
- dstack/_internal/core/backends/gcp/resources.py +6 -1
- dstack/_internal/core/backends/vastai/compute.py +2 -1
- dstack/_internal/core/errors.py +4 -0
- dstack/_internal/core/models/fleets.py +2 -0
- dstack/_internal/core/models/instances.py +2 -2
- dstack/_internal/core/models/resources.py +2 -0
- dstack/_internal/core/models/runs.py +3 -1
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +1 -1
- dstack/_internal/server/routers/gateways.py +2 -1
- dstack/_internal/server/services/config.py +7 -2
- dstack/_internal/server/services/fleets.py +15 -0
- dstack/_internal/server/services/gateways/__init__.py +17 -2
- dstack/_internal/server/services/plugins.py +77 -0
- dstack/_internal/server/services/runs.py +25 -11
- dstack/_internal/server/services/volumes.py +10 -1
- dstack/_internal/utils/common.py +10 -9
- dstack/api/server/__init__.py +8 -1
- dstack/plugins/__init__.py +8 -0
- dstack/plugins/_base.py +72 -0
- dstack/plugins/_models.py +8 -0
- dstack/plugins/_utils.py +19 -0
- dstack/version.py +1 -1
- {dstack-0.19.6rc1.dist-info → dstack-0.19.7.dist-info}/METADATA +12 -2
- {dstack-0.19.6rc1.dist-info → dstack-0.19.7.dist-info}/RECORD +31 -26
- {dstack-0.19.6rc1.dist-info → dstack-0.19.7.dist-info}/WHEEL +0 -0
- {dstack-0.19.6rc1.dist-info → dstack-0.19.7.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.6rc1.dist-info → dstack-0.19.7.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -20,6 +20,7 @@ from dstack._internal.cli.utils.rich import MultiItemStatus
|
|
|
20
20
|
from dstack._internal.core.errors import (
|
|
21
21
|
CLIError,
|
|
22
22
|
ConfigurationError,
|
|
23
|
+
MethodNotAllowedError,
|
|
23
24
|
ResourceNotExistsError,
|
|
24
25
|
ServerClientError,
|
|
25
26
|
URLNotFoundError,
|
|
@@ -321,7 +322,7 @@ def _print_plan_header(plan: FleetPlan):
|
|
|
321
322
|
offer.instance.name,
|
|
322
323
|
resources.pretty_format(),
|
|
323
324
|
"yes" if resources.spot else "no",
|
|
324
|
-
f"${offer.price:
|
|
325
|
+
f"${offer.price:3f}".rstrip("0").rstrip("."),
|
|
325
326
|
availability,
|
|
326
327
|
style=None if index == 1 else "secondary",
|
|
327
328
|
)
|
|
@@ -367,7 +368,7 @@ def _apply_plan(api: Client, plan: FleetPlan) -> Fleet:
|
|
|
367
368
|
project_name=api.project,
|
|
368
369
|
plan=plan,
|
|
369
370
|
)
|
|
370
|
-
except URLNotFoundError:
|
|
371
|
+
except (URLNotFoundError, MethodNotAllowedError):
|
|
371
372
|
# TODO: Remove in 0.20
|
|
372
373
|
return api.client.fleets.create(
|
|
373
374
|
project_name=api.project,
|
|
@@ -52,7 +52,7 @@ from dstack.api.utils import load_profile
|
|
|
52
52
|
_KNOWN_AMD_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_AMD_GPUS}
|
|
53
53
|
_KNOWN_NVIDIA_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_NVIDIA_GPUS}
|
|
54
54
|
_KNOWN_TPU_VERSIONS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_TPUS}
|
|
55
|
-
|
|
55
|
+
_KNOWN_TENSTORRENT_GPUS = {gpu.name.lower() for gpu in gpuhunt.KNOWN_TENSTORRENT_ACCELERATORS}
|
|
56
56
|
_BIND_ADDRESS_ARG = "bind_address"
|
|
57
57
|
|
|
58
58
|
logger = get_logger(__name__)
|
|
@@ -350,6 +350,7 @@ class BaseRunConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator):
|
|
|
350
350
|
if gpu_spec.count.max == 0:
|
|
351
351
|
return
|
|
352
352
|
has_amd_gpu: bool
|
|
353
|
+
has_tt_gpu: bool
|
|
353
354
|
vendor = gpu_spec.vendor
|
|
354
355
|
if vendor is None:
|
|
355
356
|
names = gpu_spec.name
|
|
@@ -362,6 +363,8 @@ class BaseRunConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator):
|
|
|
362
363
|
vendors.add(gpuhunt.AcceleratorVendor.NVIDIA)
|
|
363
364
|
elif name in _KNOWN_AMD_GPUS:
|
|
364
365
|
vendors.add(gpuhunt.AcceleratorVendor.AMD)
|
|
366
|
+
elif name in _KNOWN_TENSTORRENT_GPUS:
|
|
367
|
+
vendors.add(gpuhunt.AcceleratorVendor.TENSTORRENT)
|
|
365
368
|
else:
|
|
366
369
|
maybe_tpu_version, _, maybe_tpu_cores = name.partition("-")
|
|
367
370
|
if maybe_tpu_version in _KNOWN_TPU_VERSIONS and maybe_tpu_cores.isdigit():
|
|
@@ -380,15 +383,22 @@ class BaseRunConfigurator(ApplyEnvVarsConfiguratorMixin, BaseApplyConfigurator):
|
|
|
380
383
|
# to execute a run on an instance with an AMD accelerator with a default
|
|
381
384
|
# CUDA image, not a big deal.
|
|
382
385
|
has_amd_gpu = gpuhunt.AcceleratorVendor.AMD in vendors
|
|
386
|
+
has_tt_gpu = gpuhunt.AcceleratorVendor.TENSTORRENT in vendors
|
|
383
387
|
else:
|
|
384
388
|
# If neither gpu.vendor nor gpu.name is set, assume Nvidia.
|
|
385
389
|
vendor = gpuhunt.AcceleratorVendor.NVIDIA
|
|
386
390
|
has_amd_gpu = False
|
|
391
|
+
has_tt_gpu = False
|
|
387
392
|
gpu_spec.vendor = vendor
|
|
388
393
|
else:
|
|
389
394
|
has_amd_gpu = vendor == gpuhunt.AcceleratorVendor.AMD
|
|
395
|
+
has_tt_gpu = vendor == gpuhunt.AcceleratorVendor.TENSTORRENT
|
|
390
396
|
if has_amd_gpu and conf.image is None:
|
|
391
|
-
raise ConfigurationError("`image` is required if `resources.gpu.vendor` is
|
|
397
|
+
raise ConfigurationError("`image` is required if `resources.gpu.vendor` is `amd`")
|
|
398
|
+
if has_tt_gpu and conf.image is None:
|
|
399
|
+
raise ConfigurationError(
|
|
400
|
+
"`image` is required if `resources.gpu.vendor` is `tenstorrent`"
|
|
401
|
+
)
|
|
392
402
|
|
|
393
403
|
|
|
394
404
|
class RunWithPortsConfigurator(BaseRunConfigurator):
|
|
@@ -79,7 +79,9 @@ def get_fleets_table(
|
|
|
79
79
|
"BACKEND": backend,
|
|
80
80
|
"REGION": region,
|
|
81
81
|
"RESOURCES": resources,
|
|
82
|
-
"PRICE": f"${instance.price:.
|
|
82
|
+
"PRICE": f"${instance.price:.4f}".rstrip("0").rstrip(".")
|
|
83
|
+
if instance.price is not None
|
|
84
|
+
else "",
|
|
83
85
|
"STATUS": status,
|
|
84
86
|
"CREATED": format_date(instance.created),
|
|
85
87
|
"ERROR": error,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from typing import Any, Dict, List, Optional, Union
|
|
2
3
|
|
|
3
4
|
from rich.markup import escape
|
|
@@ -36,7 +37,7 @@ def print_run_plan(
|
|
|
36
37
|
|
|
37
38
|
req = job_plan.job_spec.requirements
|
|
38
39
|
pretty_req = req.pretty_format(resources_only=True)
|
|
39
|
-
max_price = f"${req.max_price:
|
|
40
|
+
max_price = f"${req.max_price:3f}".rstrip("0").rstrip(".") if req.max_price else "-"
|
|
40
41
|
max_duration = (
|
|
41
42
|
format_pretty_duration(job_plan.job_spec.max_duration)
|
|
42
43
|
if job_plan.job_spec.max_duration
|
|
@@ -94,14 +95,12 @@ def print_run_plan(
|
|
|
94
95
|
props.add_row(th("Inactivity duration"), inactivity_duration)
|
|
95
96
|
props.add_row(th("Reservation"), run_spec.configuration.reservation or "-")
|
|
96
97
|
|
|
97
|
-
offers = Table(box=None)
|
|
98
|
+
offers = Table(box=None, expand=os.get_terminal_size()[0] <= 110)
|
|
98
99
|
offers.add_column("#")
|
|
99
|
-
offers.add_column("BACKEND")
|
|
100
|
-
offers.add_column("
|
|
101
|
-
offers.add_column("INSTANCE TYPE")
|
|
102
|
-
offers.add_column("
|
|
103
|
-
offers.add_column("SPOT")
|
|
104
|
-
offers.add_column("PRICE")
|
|
100
|
+
offers.add_column("BACKEND", style="grey58", ratio=2)
|
|
101
|
+
offers.add_column("RESOURCES", ratio=4)
|
|
102
|
+
offers.add_column("INSTANCE TYPE", style="grey58", no_wrap=True, ratio=2)
|
|
103
|
+
offers.add_column("PRICE", style="grey58", ratio=1)
|
|
105
104
|
offers.add_column()
|
|
106
105
|
|
|
107
106
|
job_plan.offers = job_plan.offers[:max_offers] if max_offers else job_plan.offers
|
|
@@ -122,14 +121,12 @@ def print_run_plan(
|
|
|
122
121
|
instance += f" ({offer.blocks}/{offer.total_blocks})"
|
|
123
122
|
offers.add_row(
|
|
124
123
|
f"{i}",
|
|
125
|
-
offer.backend.replace("remote", "ssh"),
|
|
126
|
-
|
|
124
|
+
offer.backend.replace("remote", "ssh") + " (" + offer.region + ")",
|
|
125
|
+
r.pretty_format(include_spot=True),
|
|
127
126
|
instance,
|
|
128
|
-
|
|
129
|
-
"yes" if r.spot else "no",
|
|
130
|
-
f"${offer.price:g}",
|
|
127
|
+
f"${offer.price:.4f}".rstrip("0").rstrip("."),
|
|
131
128
|
availability,
|
|
132
|
-
style=None if i == 1 else "secondary",
|
|
129
|
+
style=None if i == 1 or not include_run_properties else "secondary",
|
|
133
130
|
)
|
|
134
131
|
if job_plan.total_offers > len(job_plan.offers):
|
|
135
132
|
offers.add_row("", "...", style="secondary")
|
|
@@ -141,7 +138,8 @@ def print_run_plan(
|
|
|
141
138
|
if job_plan.total_offers > len(job_plan.offers):
|
|
142
139
|
console.print(
|
|
143
140
|
f"[secondary] Shown {len(job_plan.offers)} of {job_plan.total_offers} offers, "
|
|
144
|
-
f"${job_plan.max_price:
|
|
141
|
+
f"${job_plan.max_price:3f}".rstrip("0").rstrip(".")
|
|
142
|
+
+ "max[/]"
|
|
145
143
|
)
|
|
146
144
|
console.print()
|
|
147
145
|
else:
|
|
@@ -151,19 +149,18 @@ def print_run_plan(
|
|
|
151
149
|
def get_runs_table(
|
|
152
150
|
runs: List[Run], verbose: bool = False, format_date: DateFormatter = pretty_date
|
|
153
151
|
) -> Table:
|
|
154
|
-
table = Table(box=None)
|
|
155
|
-
table.add_column("NAME", style="bold", no_wrap=True)
|
|
156
|
-
table.add_column("BACKEND", style="grey58")
|
|
152
|
+
table = Table(box=None, expand=os.get_terminal_size()[0] <= 110)
|
|
153
|
+
table.add_column("NAME", style="bold", no_wrap=True, ratio=2)
|
|
154
|
+
table.add_column("BACKEND", style="grey58", ratio=2)
|
|
155
|
+
table.add_column("RESOURCES", ratio=3 if not verbose else 2)
|
|
157
156
|
if verbose:
|
|
158
|
-
table.add_column("INSTANCE", no_wrap=True)
|
|
159
|
-
|
|
157
|
+
table.add_column("INSTANCE", no_wrap=True, ratio=1)
|
|
158
|
+
table.add_column("RESERVATION", no_wrap=True, ratio=1)
|
|
159
|
+
table.add_column("PRICE", style="grey58", ratio=1)
|
|
160
|
+
table.add_column("STATUS", no_wrap=True, ratio=1)
|
|
161
|
+
table.add_column("SUBMITTED", style="grey58", no_wrap=True, ratio=1)
|
|
160
162
|
if verbose:
|
|
161
|
-
table.add_column("
|
|
162
|
-
table.add_column("PRICE", no_wrap=True)
|
|
163
|
-
table.add_column("STATUS", no_wrap=True)
|
|
164
|
-
table.add_column("SUBMITTED", style="grey58", no_wrap=True)
|
|
165
|
-
if verbose:
|
|
166
|
-
table.add_column("ERROR", no_wrap=True)
|
|
163
|
+
table.add_column("ERROR", no_wrap=True, ratio=2)
|
|
167
164
|
|
|
168
165
|
for run in runs:
|
|
169
166
|
run_error = _get_run_error(run)
|
|
@@ -202,10 +199,10 @@ def get_runs_table(
|
|
|
202
199
|
job_row.update(
|
|
203
200
|
{
|
|
204
201
|
"BACKEND": f"{jpd.backend.value.replace('remote', 'ssh')} ({jpd.region})",
|
|
205
|
-
"INSTANCE": instance,
|
|
206
202
|
"RESOURCES": resources.pretty_format(include_spot=True),
|
|
203
|
+
"INSTANCE": instance,
|
|
207
204
|
"RESERVATION": jpd.reservation,
|
|
208
|
-
"PRICE": f"${jpd.price:.
|
|
205
|
+
"PRICE": f"${jpd.price:.4f}".rstrip("0").rstrip("."),
|
|
209
206
|
}
|
|
210
207
|
)
|
|
211
208
|
if len(run.jobs) == 1:
|
|
@@ -205,12 +205,17 @@ def _get_network_interfaces(
|
|
|
205
205
|
else:
|
|
206
206
|
network_interface.access_configs = []
|
|
207
207
|
|
|
208
|
+
if extra_subnetworks:
|
|
209
|
+
# Multiple interfaces are set only for GPU VM that require gVNIC for best performance
|
|
210
|
+
network_interface.nic_type = compute_v1.NetworkInterface.NicType.GVNIC.name
|
|
211
|
+
|
|
208
212
|
network_interfaces = [network_interface]
|
|
209
213
|
for network, subnetwork in extra_subnetworks or []:
|
|
210
214
|
network_interfaces.append(
|
|
211
215
|
compute_v1.NetworkInterface(
|
|
212
216
|
network=network,
|
|
213
217
|
subnetwork=subnetwork,
|
|
218
|
+
nic_type=compute_v1.NetworkInterface.NicType.GVNIC.name,
|
|
214
219
|
)
|
|
215
220
|
)
|
|
216
221
|
return network_interfaces
|
|
@@ -437,7 +442,7 @@ def wait_for_operation(operation: Operation, verbose_name: str = "operation", ti
|
|
|
437
442
|
raise
|
|
438
443
|
except Exception as e:
|
|
439
444
|
# Write only debug logs here.
|
|
440
|
-
# The unexpected errors will be propagated and logged
|
|
445
|
+
# The unexpected errors will be propagated and logged appropriately by the caller.
|
|
441
446
|
logger.debug("Error during %s: %s", verbose_name, e)
|
|
442
447
|
raise operation.exception() or e
|
|
443
448
|
return result
|
dstack/_internal/core/errors.py
CHANGED
|
@@ -269,6 +269,8 @@ class FleetSpec(CoreModel):
|
|
|
269
269
|
configuration_path: Optional[str] = None
|
|
270
270
|
profile: Profile
|
|
271
271
|
autocreated: bool = False
|
|
272
|
+
# merged_profile stores profile parameters merged from profile and configuration.
|
|
273
|
+
# Read profile parameters from merged_profile instead of profile directly.
|
|
272
274
|
# TODO: make merged_profile a computed field after migrating to pydanticV2
|
|
273
275
|
merged_profile: Annotated[Profile, Field(exclude=True)] = None
|
|
274
276
|
|
|
@@ -57,7 +57,7 @@ class Resources(CoreModel):
|
|
|
57
57
|
if self.memory_mib > 0:
|
|
58
58
|
resources["memory"] = f"{self.memory_mib / 1024:.0f}GB"
|
|
59
59
|
if self.disk.size_mib > 0:
|
|
60
|
-
resources["disk_size"] = f"{self.disk.size_mib / 1024:.
|
|
60
|
+
resources["disk_size"] = f"{self.disk.size_mib / 1024:.0f}GB"
|
|
61
61
|
if self.gpus:
|
|
62
62
|
gpu = self.gpus[0]
|
|
63
63
|
resources["gpu_name"] = gpu.name
|
|
@@ -66,7 +66,7 @@ class Resources(CoreModel):
|
|
|
66
66
|
resources["gpu_memory"] = f"{gpu.memory_mib / 1024:.0f}GB"
|
|
67
67
|
output = pretty_resources(**resources)
|
|
68
68
|
if include_spot and self.spot:
|
|
69
|
-
output += "
|
|
69
|
+
output += " (spot)"
|
|
70
70
|
return output
|
|
71
71
|
|
|
72
72
|
|
|
@@ -162,7 +162,7 @@ class Requirements(CoreModel):
|
|
|
162
162
|
if self.spot is not None:
|
|
163
163
|
res += f", {'spot' if self.spot else 'on-demand'}"
|
|
164
164
|
if self.max_price is not None:
|
|
165
|
-
res += f" under ${self.max_price:
|
|
165
|
+
res += f" under ${self.max_price:3f}".rstrip("0").rstrip(".") + " per hour"
|
|
166
166
|
return res
|
|
167
167
|
|
|
168
168
|
|
|
@@ -357,6 +357,8 @@ class RunSpec(CoreModel):
|
|
|
357
357
|
description="The contents of the SSH public key that will be used to connect to the run."
|
|
358
358
|
),
|
|
359
359
|
]
|
|
360
|
+
# merged_profile stores profile parameters merged from profile and configuration.
|
|
361
|
+
# Read profile parameters from merged_profile instead of profile directly.
|
|
360
362
|
# TODO: make merged_profile a computed field after migrating to pydanticV2
|
|
361
363
|
merged_profile: Annotated[Profile, Field(exclude=True)] = None
|
|
362
364
|
|
|
@@ -197,7 +197,7 @@ async def _process_submitted_job(session: AsyncSession, job_model: JobModel):
|
|
|
197
197
|
pool_instances = list(res.unique().scalars().all())
|
|
198
198
|
instances_ids = sorted([i.id for i in pool_instances])
|
|
199
199
|
if get_db().dialect_name == "sqlite":
|
|
200
|
-
# Start new transaction to see
|
|
200
|
+
# Start new transaction to see committed changes after lock
|
|
201
201
|
await session.commit()
|
|
202
202
|
async with get_locker().lock_ctx(InstanceModel.__tablename__, instances_ids):
|
|
203
203
|
# If another job freed the instance but is still trying to detach volumes,
|
|
@@ -47,9 +47,10 @@ async def create_gateway(
|
|
|
47
47
|
session: AsyncSession = Depends(get_session),
|
|
48
48
|
user_project: Tuple[UserModel, ProjectModel] = Depends(ProjectAdmin()),
|
|
49
49
|
) -> models.Gateway:
|
|
50
|
-
|
|
50
|
+
user, project = user_project
|
|
51
51
|
return await gateways.create_gateway(
|
|
52
52
|
session=session,
|
|
53
|
+
user=user,
|
|
53
54
|
project=project,
|
|
54
55
|
configuration=body.configuration,
|
|
55
56
|
)
|
|
@@ -29,6 +29,7 @@ from dstack._internal.server.services.permissions import (
|
|
|
29
29
|
DefaultPermissions,
|
|
30
30
|
set_default_permissions,
|
|
31
31
|
)
|
|
32
|
+
from dstack._internal.server.services.plugins import load_plugins
|
|
32
33
|
from dstack._internal.utils.logging import get_logger
|
|
33
34
|
|
|
34
35
|
logger = get_logger(__name__)
|
|
@@ -38,7 +39,7 @@ logger = get_logger(__name__)
|
|
|
38
39
|
# If a collection has nested collections, it will be assigned the block style. Otherwise it will have the flow style.
|
|
39
40
|
#
|
|
40
41
|
# We want mapping to always be displayed in block-style but lists without nested objects in flow-style.
|
|
41
|
-
# So we define a custom
|
|
42
|
+
# So we define a custom representer.
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
def seq_representer(dumper, sequence):
|
|
@@ -75,7 +76,10 @@ class ServerConfig(CoreModel):
|
|
|
75
76
|
] = None
|
|
76
77
|
default_permissions: Annotated[
|
|
77
78
|
Optional[DefaultPermissions], Field(description="The default user permissions")
|
|
78
|
-
]
|
|
79
|
+
] = None
|
|
80
|
+
plugins: Annotated[
|
|
81
|
+
Optional[List[str]], Field(description="The server-side plugins to enable")
|
|
82
|
+
] = None
|
|
79
83
|
|
|
80
84
|
|
|
81
85
|
class ServerConfigManager:
|
|
@@ -112,6 +116,7 @@ class ServerConfigManager:
|
|
|
112
116
|
await self._apply_project_config(
|
|
113
117
|
session=session, owner=owner, project_config=project_config
|
|
114
118
|
)
|
|
119
|
+
load_plugins(enabled_plugins=self.config.plugins or [])
|
|
115
120
|
|
|
116
121
|
async def _apply_project_config(
|
|
117
122
|
self,
|
|
@@ -55,6 +55,7 @@ from dstack._internal.server.services.locking import (
|
|
|
55
55
|
get_locker,
|
|
56
56
|
string_to_lock_id,
|
|
57
57
|
)
|
|
58
|
+
from dstack._internal.server.services.plugins import apply_plugin_policies
|
|
58
59
|
from dstack._internal.server.services.projects import (
|
|
59
60
|
get_member,
|
|
60
61
|
get_member_permissions,
|
|
@@ -234,7 +235,14 @@ async def get_plan(
|
|
|
234
235
|
user: UserModel,
|
|
235
236
|
spec: FleetSpec,
|
|
236
237
|
) -> FleetPlan:
|
|
238
|
+
# Spec must be copied by parsing to calculate merged_profile
|
|
237
239
|
effective_spec = FleetSpec.parse_obj(spec.dict())
|
|
240
|
+
effective_spec = apply_plugin_policies(
|
|
241
|
+
user=user.name,
|
|
242
|
+
project=project.name,
|
|
243
|
+
spec=effective_spec,
|
|
244
|
+
)
|
|
245
|
+
effective_spec = FleetSpec.parse_obj(effective_spec.dict())
|
|
238
246
|
current_fleet: Optional[Fleet] = None
|
|
239
247
|
current_fleet_id: Optional[uuid.UUID] = None
|
|
240
248
|
if effective_spec.configuration.name is not None:
|
|
@@ -330,6 +338,13 @@ async def create_fleet(
|
|
|
330
338
|
user: UserModel,
|
|
331
339
|
spec: FleetSpec,
|
|
332
340
|
) -> Fleet:
|
|
341
|
+
# Spec must be copied by parsing to calculate merged_profile
|
|
342
|
+
spec = apply_plugin_policies(
|
|
343
|
+
user=user.name,
|
|
344
|
+
project=project.name,
|
|
345
|
+
spec=spec,
|
|
346
|
+
)
|
|
347
|
+
spec = FleetSpec.parse_obj(spec.dict())
|
|
333
348
|
_validate_fleet_spec(spec)
|
|
334
349
|
|
|
335
350
|
if spec.configuration.ssh_config is not None:
|
|
@@ -31,13 +31,19 @@ from dstack._internal.core.models.gateways import (
|
|
|
31
31
|
Gateway,
|
|
32
32
|
GatewayComputeConfiguration,
|
|
33
33
|
GatewayConfiguration,
|
|
34
|
+
GatewaySpec,
|
|
34
35
|
GatewayStatus,
|
|
35
36
|
LetsEncryptGatewayCertificate,
|
|
36
37
|
)
|
|
37
38
|
from dstack._internal.core.services import validate_dstack_resource_name
|
|
38
39
|
from dstack._internal.server import settings
|
|
39
40
|
from dstack._internal.server.db import get_db
|
|
40
|
-
from dstack._internal.server.models import
|
|
41
|
+
from dstack._internal.server.models import (
|
|
42
|
+
GatewayComputeModel,
|
|
43
|
+
GatewayModel,
|
|
44
|
+
ProjectModel,
|
|
45
|
+
UserModel,
|
|
46
|
+
)
|
|
41
47
|
from dstack._internal.server.services.backends import (
|
|
42
48
|
check_backend_type_available,
|
|
43
49
|
get_project_backend_by_type_or_error,
|
|
@@ -50,6 +56,7 @@ from dstack._internal.server.services.locking import (
|
|
|
50
56
|
get_locker,
|
|
51
57
|
string_to_lock_id,
|
|
52
58
|
)
|
|
59
|
+
from dstack._internal.server.services.plugins import apply_plugin_policies
|
|
53
60
|
from dstack._internal.server.utils.common import gather_map_async
|
|
54
61
|
from dstack._internal.utils.common import get_current_datetime, run_async
|
|
55
62
|
from dstack._internal.utils.crypto import generate_rsa_key_pair_bytes
|
|
@@ -129,9 +136,17 @@ async def create_gateway_compute(
|
|
|
129
136
|
|
|
130
137
|
async def create_gateway(
|
|
131
138
|
session: AsyncSession,
|
|
139
|
+
user: UserModel,
|
|
132
140
|
project: ProjectModel,
|
|
133
141
|
configuration: GatewayConfiguration,
|
|
134
142
|
) -> Gateway:
|
|
143
|
+
spec = apply_plugin_policies(
|
|
144
|
+
user=user.name,
|
|
145
|
+
project=project.name,
|
|
146
|
+
# Create pseudo spec until the gateway API is updated to accept spec
|
|
147
|
+
spec=GatewaySpec(configuration=configuration),
|
|
148
|
+
)
|
|
149
|
+
configuration = spec.configuration
|
|
135
150
|
_validate_gateway_configuration(configuration)
|
|
136
151
|
|
|
137
152
|
backend_model, _ = await get_project_backend_with_model_by_type_or_error(
|
|
@@ -140,7 +155,7 @@ async def create_gateway(
|
|
|
140
155
|
|
|
141
156
|
lock_namespace = f"gateway_names_{project.name}"
|
|
142
157
|
if get_db().dialect_name == "sqlite":
|
|
143
|
-
# Start new transaction to see
|
|
158
|
+
# Start new transaction to see committed changes after lock
|
|
144
159
|
await session.commit()
|
|
145
160
|
elif get_db().dialect_name == "postgresql":
|
|
146
161
|
await session.execute(
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
4
|
+
from backports.entry_points_selectable import entry_points # backport for Python 3.9
|
|
5
|
+
|
|
6
|
+
from dstack._internal.core.errors import ServerClientError
|
|
7
|
+
from dstack._internal.utils.logging import get_logger
|
|
8
|
+
from dstack.plugins import ApplyPolicy, ApplySpec, Plugin
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_PLUGINS: list[Plugin] = []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_plugins(enabled_plugins: list[str]):
|
|
17
|
+
_PLUGINS.clear()
|
|
18
|
+
plugins_entrypoints = entry_points(group="dstack.plugins")
|
|
19
|
+
plugins_to_load = enabled_plugins.copy()
|
|
20
|
+
for entrypoint in plugins_entrypoints:
|
|
21
|
+
if entrypoint.name not in enabled_plugins:
|
|
22
|
+
logger.info(
|
|
23
|
+
("Found not enabled plugin %s. Plugin will not be loaded."),
|
|
24
|
+
entrypoint.name,
|
|
25
|
+
)
|
|
26
|
+
continue
|
|
27
|
+
try:
|
|
28
|
+
module_path, _, class_name = entrypoint.value.partition(":")
|
|
29
|
+
module = import_module(module_path)
|
|
30
|
+
except ImportError:
|
|
31
|
+
logger.warning(
|
|
32
|
+
(
|
|
33
|
+
"Failed to load plugin %s when importing %s."
|
|
34
|
+
" Ensure the module is on the import path."
|
|
35
|
+
),
|
|
36
|
+
entrypoint.name,
|
|
37
|
+
entrypoint.value,
|
|
38
|
+
)
|
|
39
|
+
continue
|
|
40
|
+
plugin_class = getattr(module, class_name, None)
|
|
41
|
+
if plugin_class is None:
|
|
42
|
+
logger.warning(
|
|
43
|
+
("Failed to load plugin %s: plugin class %s not found in module %s."),
|
|
44
|
+
entrypoint.name,
|
|
45
|
+
class_name,
|
|
46
|
+
module_path,
|
|
47
|
+
)
|
|
48
|
+
continue
|
|
49
|
+
if not issubclass(plugin_class, Plugin):
|
|
50
|
+
logger.warning(
|
|
51
|
+
("Failed to load plugin %s: plugin class %s is not a subclass of Plugin."),
|
|
52
|
+
entrypoint.name,
|
|
53
|
+
class_name,
|
|
54
|
+
)
|
|
55
|
+
continue
|
|
56
|
+
plugins_to_load.remove(entrypoint.name)
|
|
57
|
+
_PLUGINS.append(plugin_class())
|
|
58
|
+
logger.info("Loaded plugin %s", entrypoint.name)
|
|
59
|
+
if plugins_to_load:
|
|
60
|
+
logger.warning("Enabled plugins not found: %s", plugins_to_load)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def apply_plugin_policies(user: str, project: str, spec: ApplySpec) -> ApplySpec:
|
|
64
|
+
policies = _get_apply_policies()
|
|
65
|
+
for policy in policies:
|
|
66
|
+
try:
|
|
67
|
+
spec = policy.on_apply(user=user, project=project, spec=spec)
|
|
68
|
+
except ValueError as e:
|
|
69
|
+
msg = None
|
|
70
|
+
if len(e.args) > 0:
|
|
71
|
+
msg = e.args[0]
|
|
72
|
+
raise ServerClientError(msg)
|
|
73
|
+
return spec
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _get_apply_policies() -> list[ApplyPolicy]:
|
|
77
|
+
return list(itertools.chain(*[p.get_apply_policies() for p in _PLUGINS]))
|
|
@@ -79,6 +79,7 @@ from dstack._internal.server.services.jobs import (
|
|
|
79
79
|
from dstack._internal.server.services.locking import get_locker, string_to_lock_id
|
|
80
80
|
from dstack._internal.server.services.logging import fmt
|
|
81
81
|
from dstack._internal.server.services.offers import get_offers_by_requirements
|
|
82
|
+
from dstack._internal.server.services.plugins import apply_plugin_policies
|
|
82
83
|
from dstack._internal.server.services.projects import list_project_models, list_user_project_models
|
|
83
84
|
from dstack._internal.server.services.users import get_user_model_by_name
|
|
84
85
|
from dstack._internal.utils.logging import get_logger
|
|
@@ -279,7 +280,14 @@ async def get_plan(
|
|
|
279
280
|
run_spec: RunSpec,
|
|
280
281
|
max_offers: Optional[int],
|
|
281
282
|
) -> RunPlan:
|
|
283
|
+
# Spec must be copied by parsing to calculate merged_profile
|
|
282
284
|
effective_run_spec = RunSpec.parse_obj(run_spec.dict())
|
|
285
|
+
effective_run_spec = apply_plugin_policies(
|
|
286
|
+
user=user.name,
|
|
287
|
+
project=project.name,
|
|
288
|
+
spec=effective_run_spec,
|
|
289
|
+
)
|
|
290
|
+
effective_run_spec = RunSpec.parse_obj(effective_run_spec.dict())
|
|
283
291
|
_validate_run_spec_and_set_defaults(effective_run_spec)
|
|
284
292
|
|
|
285
293
|
profile = effective_run_spec.merged_profile
|
|
@@ -370,28 +378,36 @@ async def apply_plan(
|
|
|
370
378
|
plan: ApplyRunPlanInput,
|
|
371
379
|
force: bool,
|
|
372
380
|
) -> Run:
|
|
373
|
-
|
|
374
|
-
|
|
381
|
+
run_spec = plan.run_spec
|
|
382
|
+
run_spec = apply_plugin_policies(
|
|
383
|
+
user=user.name,
|
|
384
|
+
project=project.name,
|
|
385
|
+
spec=run_spec,
|
|
386
|
+
)
|
|
387
|
+
# Spec must be copied by parsing to calculate merged_profile
|
|
388
|
+
run_spec = RunSpec.parse_obj(run_spec.dict())
|
|
389
|
+
_validate_run_spec_and_set_defaults(run_spec)
|
|
390
|
+
if run_spec.run_name is None:
|
|
375
391
|
return await submit_run(
|
|
376
392
|
session=session,
|
|
377
393
|
user=user,
|
|
378
394
|
project=project,
|
|
379
|
-
run_spec=
|
|
395
|
+
run_spec=run_spec,
|
|
380
396
|
)
|
|
381
397
|
current_resource = await get_run_by_name(
|
|
382
398
|
session=session,
|
|
383
399
|
project=project,
|
|
384
|
-
run_name=
|
|
400
|
+
run_name=run_spec.run_name,
|
|
385
401
|
)
|
|
386
402
|
if current_resource is None or current_resource.status.is_finished():
|
|
387
403
|
return await submit_run(
|
|
388
404
|
session=session,
|
|
389
405
|
user=user,
|
|
390
406
|
project=project,
|
|
391
|
-
run_spec=
|
|
407
|
+
run_spec=run_spec,
|
|
392
408
|
)
|
|
393
409
|
try:
|
|
394
|
-
_check_can_update_run_spec(current_resource.run_spec,
|
|
410
|
+
_check_can_update_run_spec(current_resource.run_spec, run_spec)
|
|
395
411
|
except ServerClientError:
|
|
396
412
|
# The except is only needed to raise an appropriate error if run is active
|
|
397
413
|
if not current_resource.status.is_finished():
|
|
@@ -409,14 +425,12 @@ async def apply_plan(
|
|
|
409
425
|
# FIXME: potentially long write transaction
|
|
410
426
|
# Avoid getting run_model after update
|
|
411
427
|
await session.execute(
|
|
412
|
-
update(RunModel)
|
|
413
|
-
.where(RunModel.id == current_resource.id)
|
|
414
|
-
.values(run_spec=plan.run_spec.json())
|
|
428
|
+
update(RunModel).where(RunModel.id == current_resource.id).values(run_spec=run_spec.json())
|
|
415
429
|
)
|
|
416
430
|
run = await get_run_by_name(
|
|
417
431
|
session=session,
|
|
418
432
|
project=project,
|
|
419
|
-
run_name=
|
|
433
|
+
run_name=run_spec.run_name,
|
|
420
434
|
)
|
|
421
435
|
return common_utils.get_or_error(run)
|
|
422
436
|
|
|
@@ -436,7 +450,7 @@ async def submit_run(
|
|
|
436
450
|
|
|
437
451
|
lock_namespace = f"run_names_{project.name}"
|
|
438
452
|
if get_db().dialect_name == "sqlite":
|
|
439
|
-
# Start new transaction to see
|
|
453
|
+
# Start new transaction to see committed changes after lock
|
|
440
454
|
await session.commit()
|
|
441
455
|
elif get_db().dialect_name == "postgresql":
|
|
442
456
|
await session.execute(
|
|
@@ -21,6 +21,7 @@ from dstack._internal.core.models.volumes import (
|
|
|
21
21
|
VolumeConfiguration,
|
|
22
22
|
VolumeInstance,
|
|
23
23
|
VolumeProvisioningData,
|
|
24
|
+
VolumeSpec,
|
|
24
25
|
VolumeStatus,
|
|
25
26
|
)
|
|
26
27
|
from dstack._internal.core.services import validate_dstack_resource_name
|
|
@@ -38,6 +39,7 @@ from dstack._internal.server.services.locking import (
|
|
|
38
39
|
get_locker,
|
|
39
40
|
string_to_lock_id,
|
|
40
41
|
)
|
|
42
|
+
from dstack._internal.server.services.plugins import apply_plugin_policies
|
|
41
43
|
from dstack._internal.server.services.projects import list_project_models, list_user_project_models
|
|
42
44
|
from dstack._internal.utils import common, random_names
|
|
43
45
|
from dstack._internal.utils.logging import get_logger
|
|
@@ -203,11 +205,18 @@ async def create_volume(
|
|
|
203
205
|
user: UserModel,
|
|
204
206
|
configuration: VolumeConfiguration,
|
|
205
207
|
) -> Volume:
|
|
208
|
+
spec = apply_plugin_policies(
|
|
209
|
+
user=user.name,
|
|
210
|
+
project=project.name,
|
|
211
|
+
# Create pseudo spec until the volume API is updated to accept spec
|
|
212
|
+
spec=VolumeSpec(configuration=configuration),
|
|
213
|
+
)
|
|
214
|
+
configuration = spec.configuration
|
|
206
215
|
_validate_volume_configuration(configuration)
|
|
207
216
|
|
|
208
217
|
lock_namespace = f"volume_names_{project.name}"
|
|
209
218
|
if get_db().dialect_name == "sqlite":
|
|
210
|
-
# Start new transaction to see
|
|
219
|
+
# Start new transaction to see committed changes after lock
|
|
211
220
|
await session.commit()
|
|
212
221
|
elif get_db().dialect_name == "postgresql":
|
|
213
222
|
await session.execute(
|
dstack/_internal/utils/common.py
CHANGED
|
@@ -110,25 +110,26 @@ def pretty_resources(
|
|
|
110
110
|
"""
|
|
111
111
|
parts = []
|
|
112
112
|
if cpus is not None:
|
|
113
|
-
parts.append(f"{cpus}
|
|
113
|
+
parts.append(f"cpu={cpus}")
|
|
114
114
|
if memory is not None:
|
|
115
|
-
parts.append(f"{memory}")
|
|
115
|
+
parts.append(f"mem={memory}")
|
|
116
|
+
if disk_size:
|
|
117
|
+
parts.append(f"disk={disk_size}")
|
|
116
118
|
if gpu_count:
|
|
117
119
|
gpu_parts = []
|
|
120
|
+
gpu_parts.append(f"{gpu_name or 'gpu'}")
|
|
118
121
|
if gpu_memory is not None:
|
|
119
122
|
gpu_parts.append(f"{gpu_memory}")
|
|
123
|
+
if gpu_count is not None:
|
|
124
|
+
gpu_parts.append(f"{gpu_count}")
|
|
120
125
|
if total_gpu_memory is not None:
|
|
121
|
-
gpu_parts.append(f"
|
|
126
|
+
gpu_parts.append(f"{total_gpu_memory}")
|
|
122
127
|
if compute_capability is not None:
|
|
123
128
|
gpu_parts.append(f"{compute_capability}")
|
|
124
129
|
|
|
125
|
-
gpu =
|
|
126
|
-
if gpu_parts:
|
|
127
|
-
gpu += f" ({', '.join(gpu_parts)})"
|
|
130
|
+
gpu = ":".join(gpu_parts)
|
|
128
131
|
parts.append(gpu)
|
|
129
|
-
|
|
130
|
-
parts.append(f"{disk_size} (disk)")
|
|
131
|
-
return ", ".join(parts)
|
|
132
|
+
return " ".join(parts)
|
|
132
133
|
|
|
133
134
|
|
|
134
135
|
def since(timestamp: str) -> datetime:
|
dstack/api/server/__init__.py
CHANGED
|
@@ -6,7 +6,12 @@ from typing import Dict, List, Optional, Type
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
8
|
from dstack import version
|
|
9
|
-
from dstack._internal.core.errors import
|
|
9
|
+
from dstack._internal.core.errors import (
|
|
10
|
+
ClientError,
|
|
11
|
+
MethodNotAllowedError,
|
|
12
|
+
ServerClientError,
|
|
13
|
+
URLNotFoundError,
|
|
14
|
+
)
|
|
10
15
|
from dstack._internal.utils.logging import get_logger
|
|
11
16
|
from dstack.api.server._backends import BackendsAPIClient
|
|
12
17
|
from dstack.api.server._fleets import FleetsAPIClient
|
|
@@ -156,6 +161,8 @@ class APIClient:
|
|
|
156
161
|
)
|
|
157
162
|
if resp.status_code == 404:
|
|
158
163
|
raise URLNotFoundError(f"Status code 404 when requesting {resp.request.url}")
|
|
164
|
+
if resp.status_code == 405:
|
|
165
|
+
raise MethodNotAllowedError(f"Status code 405 when requesting {resp.request.url}")
|
|
159
166
|
if 400 <= resp.status_code < 600:
|
|
160
167
|
raise ClientError(
|
|
161
168
|
f"Unexpected error: status code {resp.status_code}"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# ruff: noqa: F401
|
|
2
|
+
from dstack._internal.core.models.fleets import FleetSpec
|
|
3
|
+
from dstack._internal.core.models.gateways import GatewaySpec
|
|
4
|
+
from dstack._internal.core.models.runs import RunSpec
|
|
5
|
+
from dstack._internal.core.models.volumes import VolumeSpec
|
|
6
|
+
from dstack.plugins._base import ApplyPolicy, Plugin
|
|
7
|
+
from dstack.plugins._models import ApplySpec
|
|
8
|
+
from dstack.plugins._utils import get_plugin_logger
|
dstack/plugins/_base.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from dstack._internal.core.models.fleets import FleetSpec
|
|
2
|
+
from dstack._internal.core.models.gateways import GatewaySpec
|
|
3
|
+
from dstack._internal.core.models.runs import RunSpec
|
|
4
|
+
from dstack._internal.core.models.volumes import VolumeSpec
|
|
5
|
+
from dstack.plugins._models import ApplySpec
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ApplyPolicy:
|
|
9
|
+
"""
|
|
10
|
+
A base apply policy class to modify specs on `dstack apply`.
|
|
11
|
+
Subclass it and return the subclass instance in `Plugin.get_apply_policies()`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def on_apply(self, user: str, project: str, spec: ApplySpec) -> ApplySpec:
|
|
15
|
+
"""
|
|
16
|
+
Modify `spec` before it's applied.
|
|
17
|
+
Raise `ValueError` for `spec` to be rejected as invalid.
|
|
18
|
+
|
|
19
|
+
This method can be called twice:
|
|
20
|
+
* first when a user gets a plan
|
|
21
|
+
* second when a user applies a plan
|
|
22
|
+
|
|
23
|
+
In both cases, the original spec is passed, so the method does not
|
|
24
|
+
need to check if it modified the spec before.
|
|
25
|
+
|
|
26
|
+
It's safe to modify and return `spec` without copying.
|
|
27
|
+
"""
|
|
28
|
+
if isinstance(spec, RunSpec):
|
|
29
|
+
return self.on_run_apply(user=user, project=project, spec=spec)
|
|
30
|
+
if isinstance(spec, FleetSpec):
|
|
31
|
+
return self.on_fleet_apply(user=user, project=project, spec=spec)
|
|
32
|
+
if isinstance(spec, VolumeSpec):
|
|
33
|
+
return self.on_volume_apply(user=user, project=project, spec=spec)
|
|
34
|
+
if isinstance(spec, GatewaySpec):
|
|
35
|
+
return self.on_gateway_apply(user=user, project=project, spec=spec)
|
|
36
|
+
raise ValueError(f"Unknown spec type {type(spec)}")
|
|
37
|
+
|
|
38
|
+
def on_run_apply(self, user: str, project: str, spec: RunSpec) -> RunSpec:
|
|
39
|
+
"""
|
|
40
|
+
Called by the default `on_apply()` implementation for runs.
|
|
41
|
+
"""
|
|
42
|
+
return spec
|
|
43
|
+
|
|
44
|
+
def on_fleet_apply(self, user: str, project: str, spec: FleetSpec) -> FleetSpec:
|
|
45
|
+
"""
|
|
46
|
+
Called by the default `on_apply()` implementation for fleets.
|
|
47
|
+
"""
|
|
48
|
+
return spec
|
|
49
|
+
|
|
50
|
+
def on_volume_apply(self, user: str, project: str, spec: VolumeSpec) -> VolumeSpec:
|
|
51
|
+
"""
|
|
52
|
+
Called by the default `on_apply()` implementation for volumes.
|
|
53
|
+
"""
|
|
54
|
+
return spec
|
|
55
|
+
|
|
56
|
+
def on_gateway_apply(self, user: str, project: str, spec: GatewaySpec) -> GatewaySpec:
|
|
57
|
+
"""
|
|
58
|
+
Called by the default `on_apply()` implementation for gateways.
|
|
59
|
+
"""
|
|
60
|
+
return spec
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Plugin:
|
|
64
|
+
"""
|
|
65
|
+
A base plugin class.
|
|
66
|
+
Plugins must subclass it, implement public methods,
|
|
67
|
+
and register the subclass as an entrypoint of the package
|
|
68
|
+
(https://packaging.python.org/en/latest/specifications/entry-points/).
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def get_apply_policies(self) -> list[ApplyPolicy]:
|
|
72
|
+
return []
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from dstack._internal.core.models.fleets import FleetSpec
|
|
4
|
+
from dstack._internal.core.models.gateways import GatewaySpec
|
|
5
|
+
from dstack._internal.core.models.runs import RunSpec
|
|
6
|
+
from dstack._internal.core.models.volumes import VolumeSpec
|
|
7
|
+
|
|
8
|
+
ApplySpec = TypeVar("ApplySpec", RunSpec, FleetSpec, VolumeSpec, GatewaySpec)
|
dstack/plugins/_utils.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from dstack._internal.utils.logging import get_logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_plugin_logger(name: str) -> logging.Logger:
|
|
7
|
+
"""
|
|
8
|
+
Use this function to set up loggers in plugins.
|
|
9
|
+
|
|
10
|
+
Put at the top of the plugin modules:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
from dstack.plugins import get_plugin_logger
|
|
14
|
+
|
|
15
|
+
logger = get_plugin_logger(__name__)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
return get_logger(f"dstack.plugins.{name}")
|
dstack/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dstack
|
|
3
|
-
Version: 0.19.
|
|
3
|
+
Version: 0.19.7
|
|
4
4
|
Summary: dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises.
|
|
5
5
|
Project-URL: Homepage, https://dstack.ai
|
|
6
6
|
Project-URL: Source, https://github.com/dstackai/dstack
|
|
@@ -21,7 +21,7 @@ Requires-Dist: cryptography
|
|
|
21
21
|
Requires-Dist: cursor
|
|
22
22
|
Requires-Dist: filelock
|
|
23
23
|
Requires-Dist: gitpython
|
|
24
|
-
Requires-Dist: gpuhunt
|
|
24
|
+
Requires-Dist: gpuhunt==0.1.5
|
|
25
25
|
Requires-Dist: jsonschema
|
|
26
26
|
Requires-Dist: packaging
|
|
27
27
|
Requires-Dist: paramiko>=3.2.0
|
|
@@ -52,6 +52,7 @@ Requires-Dist: azure-mgmt-compute>=29.1.0; extra == 'all'
|
|
|
52
52
|
Requires-Dist: azure-mgmt-network<28.0.0,>=23.0.0; extra == 'all'
|
|
53
53
|
Requires-Dist: azure-mgmt-resource>=22.0.0; extra == 'all'
|
|
54
54
|
Requires-Dist: azure-mgmt-subscription>=3.1.1; extra == 'all'
|
|
55
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'all'
|
|
55
56
|
Requires-Dist: boto3; extra == 'all'
|
|
56
57
|
Requires-Dist: botocore; extra == 'all'
|
|
57
58
|
Requires-Dist: datacrunch; extra == 'all'
|
|
@@ -87,6 +88,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'aws'
|
|
|
87
88
|
Requires-Dist: alembic>=1.10.2; extra == 'aws'
|
|
88
89
|
Requires-Dist: apscheduler<4; extra == 'aws'
|
|
89
90
|
Requires-Dist: asyncpg; extra == 'aws'
|
|
91
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'aws'
|
|
90
92
|
Requires-Dist: boto3; extra == 'aws'
|
|
91
93
|
Requires-Dist: botocore; extra == 'aws'
|
|
92
94
|
Requires-Dist: docker>=6.0.0; extra == 'aws'
|
|
@@ -117,6 +119,7 @@ Requires-Dist: azure-mgmt-compute>=29.1.0; extra == 'azure'
|
|
|
117
119
|
Requires-Dist: azure-mgmt-network<28.0.0,>=23.0.0; extra == 'azure'
|
|
118
120
|
Requires-Dist: azure-mgmt-resource>=22.0.0; extra == 'azure'
|
|
119
121
|
Requires-Dist: azure-mgmt-subscription>=3.1.1; extra == 'azure'
|
|
122
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'azure'
|
|
120
123
|
Requires-Dist: docker>=6.0.0; extra == 'azure'
|
|
121
124
|
Requires-Dist: fastapi; extra == 'azure'
|
|
122
125
|
Requires-Dist: grpcio>=1.50; extra == 'azure'
|
|
@@ -139,6 +142,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'datacrunch'
|
|
|
139
142
|
Requires-Dist: alembic>=1.10.2; extra == 'datacrunch'
|
|
140
143
|
Requires-Dist: apscheduler<4; extra == 'datacrunch'
|
|
141
144
|
Requires-Dist: asyncpg; extra == 'datacrunch'
|
|
145
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'datacrunch'
|
|
142
146
|
Requires-Dist: datacrunch; extra == 'datacrunch'
|
|
143
147
|
Requires-Dist: docker>=6.0.0; extra == 'datacrunch'
|
|
144
148
|
Requires-Dist: fastapi; extra == 'datacrunch'
|
|
@@ -170,6 +174,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'gcp'
|
|
|
170
174
|
Requires-Dist: alembic>=1.10.2; extra == 'gcp'
|
|
171
175
|
Requires-Dist: apscheduler<4; extra == 'gcp'
|
|
172
176
|
Requires-Dist: asyncpg; extra == 'gcp'
|
|
177
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'gcp'
|
|
173
178
|
Requires-Dist: docker>=6.0.0; extra == 'gcp'
|
|
174
179
|
Requires-Dist: fastapi; extra == 'gcp'
|
|
175
180
|
Requires-Dist: google-api-python-client>=2.80.0; extra == 'gcp'
|
|
@@ -199,6 +204,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'kubernetes'
|
|
|
199
204
|
Requires-Dist: alembic>=1.10.2; extra == 'kubernetes'
|
|
200
205
|
Requires-Dist: apscheduler<4; extra == 'kubernetes'
|
|
201
206
|
Requires-Dist: asyncpg; extra == 'kubernetes'
|
|
207
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'kubernetes'
|
|
202
208
|
Requires-Dist: docker>=6.0.0; extra == 'kubernetes'
|
|
203
209
|
Requires-Dist: fastapi; extra == 'kubernetes'
|
|
204
210
|
Requires-Dist: grpcio>=1.50; extra == 'kubernetes'
|
|
@@ -222,6 +228,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'lambda'
|
|
|
222
228
|
Requires-Dist: alembic>=1.10.2; extra == 'lambda'
|
|
223
229
|
Requires-Dist: apscheduler<4; extra == 'lambda'
|
|
224
230
|
Requires-Dist: asyncpg; extra == 'lambda'
|
|
231
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'lambda'
|
|
225
232
|
Requires-Dist: boto3; extra == 'lambda'
|
|
226
233
|
Requires-Dist: botocore; extra == 'lambda'
|
|
227
234
|
Requires-Dist: docker>=6.0.0; extra == 'lambda'
|
|
@@ -246,6 +253,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'nebius'
|
|
|
246
253
|
Requires-Dist: alembic>=1.10.2; extra == 'nebius'
|
|
247
254
|
Requires-Dist: apscheduler<4; extra == 'nebius'
|
|
248
255
|
Requires-Dist: asyncpg; extra == 'nebius'
|
|
256
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'nebius'
|
|
249
257
|
Requires-Dist: docker>=6.0.0; extra == 'nebius'
|
|
250
258
|
Requires-Dist: fastapi; extra == 'nebius'
|
|
251
259
|
Requires-Dist: grpcio>=1.50; extra == 'nebius'
|
|
@@ -269,6 +277,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'oci'
|
|
|
269
277
|
Requires-Dist: alembic>=1.10.2; extra == 'oci'
|
|
270
278
|
Requires-Dist: apscheduler<4; extra == 'oci'
|
|
271
279
|
Requires-Dist: asyncpg; extra == 'oci'
|
|
280
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'oci'
|
|
272
281
|
Requires-Dist: docker>=6.0.0; extra == 'oci'
|
|
273
282
|
Requires-Dist: fastapi; extra == 'oci'
|
|
274
283
|
Requires-Dist: grpcio>=1.50; extra == 'oci'
|
|
@@ -292,6 +301,7 @@ Requires-Dist: alembic-postgresql-enum; extra == 'server'
|
|
|
292
301
|
Requires-Dist: alembic>=1.10.2; extra == 'server'
|
|
293
302
|
Requires-Dist: apscheduler<4; extra == 'server'
|
|
294
303
|
Requires-Dist: asyncpg; extra == 'server'
|
|
304
|
+
Requires-Dist: backports-entry-points-selectable; extra == 'server'
|
|
295
305
|
Requires-Dist: docker>=6.0.0; extra == 'server'
|
|
296
306
|
Requires-Dist: fastapi; extra == 'server'
|
|
297
307
|
Requires-Dist: grpcio>=1.50; extra == 'server'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
dstack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
dstack/version.py,sha256=
|
|
2
|
+
dstack/version.py,sha256=ikdWwuUYtHtNaJ5eN6aIj36R0uhZ5NlYDVtxl3E_02M,64
|
|
3
3
|
dstack/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
dstack/_internal/compat.py,sha256=bF9U9fTMfL8UVhCouedoUSTYFl7UAOiU0WXrnRoByxw,40
|
|
5
5
|
dstack/_internal/settings.py,sha256=otvcNT0X5UnGZdoNIWNFZBohQRzLme9Zc6oiBzc1BEk,796
|
|
@@ -29,21 +29,21 @@ dstack/_internal/cli/services/profile.py,sha256=zfLRuesv5DSnLkbOU-TouvYmGGaKvw33
|
|
|
29
29
|
dstack/_internal/cli/services/repos.py,sha256=ImJuElGjsR4OQ7dBquy4PCQUbBM4bR4vhyLwMrVBS7k,3307
|
|
30
30
|
dstack/_internal/cli/services/configurators/__init__.py,sha256=z94VPBFqybP8Zpwy3CzYxmpPAqYBOvRRLpXoz2H4GKI,2697
|
|
31
31
|
dstack/_internal/cli/services/configurators/base.py,sha256=bGfde2zoma28lLE8MUACO4-NKT1CdJJQJoXrzjpz0mQ,3360
|
|
32
|
-
dstack/_internal/cli/services/configurators/fleet.py,sha256=
|
|
32
|
+
dstack/_internal/cli/services/configurators/fleet.py,sha256=jm4tNH6QQVplLdboCTlvRYUee3nZ0UYb_qLTrvtYVYM,14049
|
|
33
33
|
dstack/_internal/cli/services/configurators/gateway.py,sha256=czB2s89s7IowOmWnpDwWErPAUlW3FvFMizImhrkQiBM,8927
|
|
34
|
-
dstack/_internal/cli/services/configurators/run.py,sha256=
|
|
34
|
+
dstack/_internal/cli/services/configurators/run.py,sha256=arYV5pDAtA4ZJSwY1y4OOYeJpi4JLDteHM2ubI6dCaw,23710
|
|
35
35
|
dstack/_internal/cli/services/configurators/volume.py,sha256=riMXLQbgvHIIFwLKdHfad-_0iE9wE3G_rUmXU5P3ZS8,8519
|
|
36
36
|
dstack/_internal/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
37
|
dstack/_internal/cli/utils/common.py,sha256=rfmzqrsgR3rXW3wj0vxDdvrhUUg2aIy4A6E9MZbd55g,1763
|
|
38
|
-
dstack/_internal/cli/utils/fleet.py,sha256=
|
|
38
|
+
dstack/_internal/cli/utils/fleet.py,sha256=ch-LN1X9boSm-rFLW4mAJRmz0XliLhH0LvKD2DqSt2g,3942
|
|
39
39
|
dstack/_internal/cli/utils/gateway.py,sha256=qMYa1NTAT_O98x2_mSyWDRbiHj5fqt6xUXFh9NIUwAM,1502
|
|
40
40
|
dstack/_internal/cli/utils/rich.py,sha256=Gx1MJU929kMKsbdo9qF7XHARNta2426Ssb-xMLVhwbQ,5710
|
|
41
|
-
dstack/_internal/cli/utils/run.py,sha256=
|
|
41
|
+
dstack/_internal/cli/utils/run.py,sha256=30K28Y8fnbcME7kVZv8iflpH4FyyC50XehrKLYvMYZ4,8965
|
|
42
42
|
dstack/_internal/cli/utils/updates.py,sha256=sAPYYptkFzQnGaRjv7FV7HOj-Be3IXGe63xj-sVEpv4,2566
|
|
43
43
|
dstack/_internal/cli/utils/volume.py,sha256=mU9I06dVMFbpjfkefxrZNoSWadKLoib3U14rHudNQN4,1975
|
|
44
44
|
dstack/_internal/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
45
|
dstack/_internal/core/consts.py,sha256=c1Yd5UY6Qx7KeuYgloXWncWhMsYj6TqwlElda7NtB98,254
|
|
46
|
-
dstack/_internal/core/errors.py,sha256=
|
|
46
|
+
dstack/_internal/core/errors.py,sha256=tJcZXwrvWvvtzIqkzLxJJMrhDuAQl8eOk0xpKCqqHzw,3313
|
|
47
47
|
dstack/_internal/core/backends/__init__.py,sha256=fwgV8CN8Ap6MZmWklMGHHf0roliBtqne-ijjVOpWcgc,2467
|
|
48
48
|
dstack/_internal/core/backends/configurators.py,sha256=JxGfZwcmL90akMFkAzzZ_fzPvU2No0pBaBqU_g0D-y0,3775
|
|
49
49
|
dstack/_internal/core/backends/models.py,sha256=aKQOrDEStouuwY4MacSen7SkoyAa6HR6a6PFq5-cbNk,4088
|
|
@@ -87,7 +87,7 @@ dstack/_internal/core/backends/gcp/backend.py,sha256=OvTv1c7j4LTPCIEtkwD3-q6Eo1Q
|
|
|
87
87
|
dstack/_internal/core/backends/gcp/compute.py,sha256=uDPoDoqx6S29bAZBQk-Bwoz_u6oMfQB8u2FpMLZ-uDg,40815
|
|
88
88
|
dstack/_internal/core/backends/gcp/configurator.py,sha256=mvI7WMz8cC1YnN-0KFIIEqkfcEBehRJI0WgKnqILjv0,6730
|
|
89
89
|
dstack/_internal/core/backends/gcp/models.py,sha256=biLA3rlFcoPatAZpKycuIl-8PdnNSAFiDCJjov65_zo,4612
|
|
90
|
-
dstack/_internal/core/backends/gcp/resources.py,sha256=
|
|
90
|
+
dstack/_internal/core/backends/gcp/resources.py,sha256=gLoRo9Z1--n55JTEDVaofUsBo-8h5Nfbhsb_2ydvmK8,16487
|
|
91
91
|
dstack/_internal/core/backends/gcp/features/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
92
|
dstack/_internal/core/backends/gcp/features/tcpx.py,sha256=8bDR5kwF5qke5EWNdBscdbZQnC7oVXKSls3WPcoXgZI,2902
|
|
93
93
|
dstack/_internal/core/backends/kubernetes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -142,7 +142,7 @@ dstack/_internal/core/backends/tensordock/models.py,sha256=oxbSWRCsD0_j4-2lQYo4L
|
|
|
142
142
|
dstack/_internal/core/backends/vastai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
143
|
dstack/_internal/core/backends/vastai/api_client.py,sha256=b-qgx352lnxhBXichJtURFGxR7Jd9JYx4WXaxYV-HH0,4940
|
|
144
144
|
dstack/_internal/core/backends/vastai/backend.py,sha256=NawGHNYVmgNwL8HruEA84rimnpeSyXccdJXr6Dr95UY,566
|
|
145
|
-
dstack/_internal/core/backends/vastai/compute.py,sha256=
|
|
145
|
+
dstack/_internal/core/backends/vastai/compute.py,sha256=ZjeJF196lXTjO5gQ8fnQtvNEVZdYpcVODmZOOV3g96Q,4911
|
|
146
146
|
dstack/_internal/core/backends/vastai/configurator.py,sha256=mn8Sx0ZFgTlBa2mf2xNjGymXhbfswgHTjYDUnTiI028,2267
|
|
147
147
|
dstack/_internal/core/backends/vastai/models.py,sha256=2xpFE-ouYUBienxZXam2Mo4dy9enSv-FuVJXeYnjVLk,1036
|
|
148
148
|
dstack/_internal/core/backends/vultr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -156,16 +156,16 @@ dstack/_internal/core/models/common.py,sha256=XWd79dmFGMrdpTcStH5fVmNXCKE0s7FsIo
|
|
|
156
156
|
dstack/_internal/core/models/config.py,sha256=JJ7rT7dztzTWCY5TkoyxXxTvG5D4IFYhGe7EzwkLOWQ,581
|
|
157
157
|
dstack/_internal/core/models/configurations.py,sha256=c2717pYpna6u2bmtmC3DlX67HtQa-Tud387mUVa4vEg,18498
|
|
158
158
|
dstack/_internal/core/models/envs.py,sha256=yq84YRFBILOy4x3XnGcTgYpbZ69eFTCQPgBCr9Ndov4,4969
|
|
159
|
-
dstack/_internal/core/models/fleets.py,sha256=
|
|
159
|
+
dstack/_internal/core/models/fleets.py,sha256=gbP2rj1ODDuy4IfTbPNB-ae050thvzTtA8uDb5iTmdY,12059
|
|
160
160
|
dstack/_internal/core/models/gateways.py,sha256=_O8EWwHWLdgNoWY4P4u71KM-uEr5DDp42LXfyv1qMDI,4054
|
|
161
|
-
dstack/_internal/core/models/instances.py,sha256=
|
|
161
|
+
dstack/_internal/core/models/instances.py,sha256=gwJFF0wJhPQCd0Y5ZiFD9e4_EADP3ktHMurxGV0QR18,5393
|
|
162
162
|
dstack/_internal/core/models/logs.py,sha256=Lsmtd_NrnChMjBJahUZpFb1j8Xobix9FHWf1L47FOGs,443
|
|
163
163
|
dstack/_internal/core/models/metrics.py,sha256=Xb8hCXUL-ncQ3PMsErIUAJTe9gwh5jyrQ4UQoZbibsc,269
|
|
164
164
|
dstack/_internal/core/models/placement.py,sha256=WJVq5ENJykyRarQzL2EeYQag_9_jV7VSAtR_xoFvPVM,720
|
|
165
165
|
dstack/_internal/core/models/profiles.py,sha256=seeysTuMv1vVUmpHAZgrMUGcbMtH7hSMFIvfx0Qk__0,10406
|
|
166
166
|
dstack/_internal/core/models/projects.py,sha256=H5ZZRiyUEKifpTFAhl45KBi5ly7ooE0WmI329myK360,643
|
|
167
|
-
dstack/_internal/core/models/resources.py,sha256=
|
|
168
|
-
dstack/_internal/core/models/runs.py,sha256=
|
|
167
|
+
dstack/_internal/core/models/resources.py,sha256=xvPuRlF4y3Pj_HNBJTmmzuroTggQQsaR87KERu6asN0,11380
|
|
168
|
+
dstack/_internal/core/models/runs.py,sha256=RvUJHIP1h_GAYiqvTwJxnW46AipEsM92pB33cv21Mn4,18391
|
|
169
169
|
dstack/_internal/core/models/secrets.py,sha256=IQyemsNpSzqOCB-VlVTuc4gyPFmXXO4mhko0Ur0ey3I,221
|
|
170
170
|
dstack/_internal/core/models/server.py,sha256=Hkc1v2s3KOiwslsWVmhUOAzcSeREoG-HD1SzSX9WUGg,152
|
|
171
171
|
dstack/_internal/core/models/services.py,sha256=2Hpi7j0Q1shaf_0wd0C0044AJAmuYi-D3qx3PH849oI,3076
|
|
@@ -262,7 +262,7 @@ dstack/_internal/server/background/tasks/process_placement_groups.py,sha256=FqGf
|
|
|
262
262
|
dstack/_internal/server/background/tasks/process_prometheus_metrics.py,sha256=u8hCXjOOek7VLEsmLy2VnDXFmIwTNjrJwcpWG7a1zW0,5093
|
|
263
263
|
dstack/_internal/server/background/tasks/process_running_jobs.py,sha256=U6JdkEnpIApbiSRLKxqjNwA9WFAZY2zZNXujofhUd_g,34719
|
|
264
264
|
dstack/_internal/server/background/tasks/process_runs.py,sha256=EI1W6HUyB-og3g8BDP_GsBrJjQ-Z3JvZHTuJf7CRKRM,17974
|
|
265
|
-
dstack/_internal/server/background/tasks/process_submitted_jobs.py,sha256
|
|
265
|
+
dstack/_internal/server/background/tasks/process_submitted_jobs.py,sha256=-XOApBgmn9ZyCoeXgnbp6cnsFT3uxE_-xqLtn1ez5dc,26603
|
|
266
266
|
dstack/_internal/server/background/tasks/process_terminating_jobs.py,sha256=0Z3Q409RwSxOL_pgK8JktBthjtESEUH3ahwTLsTdYPk,3800
|
|
267
267
|
dstack/_internal/server/background/tasks/process_volumes.py,sha256=206rbT4ICeZtEmqh_94Rry_fgHfFLLaSEX9W-svwFk4,5089
|
|
268
268
|
dstack/_internal/server/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -336,7 +336,7 @@ dstack/_internal/server/migrations/versions/ffa99edd1988_add_jobterminationreaso
|
|
|
336
336
|
dstack/_internal/server/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
337
337
|
dstack/_internal/server/routers/backends.py,sha256=vSCP-wbH-fsoKDR1TrMyQCz5UA-biiY0Lo7ERVz_L_g,4684
|
|
338
338
|
dstack/_internal/server/routers/fleets.py,sha256=ikXeJ7phoVocU3i-_S6V5RIMygjh-KHuhEIAS5Cw1wo,5565
|
|
339
|
-
dstack/_internal/server/routers/gateways.py,sha256=
|
|
339
|
+
dstack/_internal/server/routers/gateways.py,sha256=K_VG5Dt_2F_cykWjtFj4GBwtm_D3xfsE3Bf-lDgQx_w,3171
|
|
340
340
|
dstack/_internal/server/routers/instances.py,sha256=XOogTC9My2Zv0ck37_PbHKoZI-j4QeGrP2sN5wpX7Ow,1579
|
|
341
341
|
dstack/_internal/server/routers/logs.py,sha256=_Euk283LbhlwHibJTKM-7YcpbeQFtWBqMfbOry3PSkU,1159
|
|
342
342
|
dstack/_internal/server/routers/metrics.py,sha256=VFgWhkOvxVFDLlRM_kXHYFylLcfCD6UjXInvcd7H4dY,2314
|
|
@@ -365,9 +365,9 @@ dstack/_internal/server/schemas/volumes.py,sha256=9iwaQLMhA6aj9XmtdU_9jWVhpzNOtF
|
|
|
365
365
|
dstack/_internal/server/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
366
366
|
dstack/_internal/server/security/permissions.py,sha256=FJ_8YPhjmebA4jQjtQoAGEaj1Hahb_po0tYRCQ18aaE,4940
|
|
367
367
|
dstack/_internal/server/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
368
|
-
dstack/_internal/server/services/config.py,sha256=
|
|
368
|
+
dstack/_internal/server/services/config.py,sha256=yo8njslwfS7_blhbPPhOtzCMyg8N_mmFSw5aPvirSzw,10691
|
|
369
369
|
dstack/_internal/server/services/docker.py,sha256=zAvjFHxIP03Td92NzbGEScz0piLjloE60tQ7vz0AeCA,5328
|
|
370
|
-
dstack/_internal/server/services/fleets.py,sha256=
|
|
370
|
+
dstack/_internal/server/services/fleets.py,sha256=JQuXwunED_m_gvLeAO6cUVxeeRKTFAlBHNqQKuvLUZ8,27076
|
|
371
371
|
dstack/_internal/server/services/instances.py,sha256=omCzPWEIRJQx7WkwyJPmxMtkjas9KtgIFl-zLtnurBY,18610
|
|
372
372
|
dstack/_internal/server/services/locking.py,sha256=7JUgNSplKRx7dxC4LIpmWw81agUtslEDTeDiNMPbAVg,3013
|
|
373
373
|
dstack/_internal/server/services/logging.py,sha256=Nu1628kW2hqB__N0Eyr07wGWjVWxfyJnczonTJ72kSM,417
|
|
@@ -375,13 +375,14 @@ dstack/_internal/server/services/metrics.py,sha256=jKLy1jSCVR_crqVu_CmsOMbvMkucW
|
|
|
375
375
|
dstack/_internal/server/services/offers.py,sha256=At_fRbFCEHBsiJiqIZMMowofO4o0pfs6tSOtFgZc8Nc,7086
|
|
376
376
|
dstack/_internal/server/services/permissions.py,sha256=l7Ngdelmn65vjw13NcOdaC6lBYMRuSw6FbHzYwdK3nE,1005
|
|
377
377
|
dstack/_internal/server/services/placement.py,sha256=DWZ8-iAE3o0J0xaHikuJYZzpuBiq7lj41LiAP1PfoEs,1773
|
|
378
|
+
dstack/_internal/server/services/plugins.py,sha256=e5ESJVw5EaxkmxLXdvZdIGaFNxy8k5QOiPQD2zN9w84,2650
|
|
378
379
|
dstack/_internal/server/services/projects.py,sha256=Je1iWZ-ArmyFxK1yMUzod5WRXyiIDxtuVp6pHcdctTQ,14988
|
|
379
380
|
dstack/_internal/server/services/prometheus.py,sha256=xq5G-Q2BJup9lS2F6__0wUVTs-k1Gr3dYclGzo2WoWo,12474
|
|
380
381
|
dstack/_internal/server/services/repos.py,sha256=f9ztN7jz_2gvD9hXF5sJwWDVyG2-NHRfjIdSukowPh8,9342
|
|
381
|
-
dstack/_internal/server/services/runs.py,sha256=
|
|
382
|
+
dstack/_internal/server/services/runs.py,sha256=W8MuRchSGijmFZo7TCdm2ko0LnICUGc1jtOz0qt5p5E,37824
|
|
382
383
|
dstack/_internal/server/services/storage.py,sha256=6I0xI_3_RpJNbKZwHjDnjrEwXGdHfiaeb5li15T-M1I,1884
|
|
383
384
|
dstack/_internal/server/services/users.py,sha256=W-5xL7zsHNjeG7BBK54RWGvIrBOrw-FF0NcG_z9qhoE,7466
|
|
384
|
-
dstack/_internal/server/services/volumes.py,sha256=
|
|
385
|
+
dstack/_internal/server/services/volumes.py,sha256=vfKY6eZp64I58Mfdvrk9Wig7deveD2Rw4ET1cbc1Sog,16238
|
|
385
386
|
dstack/_internal/server/services/backends/__init__.py,sha256=Aqo1GoqhZ_FsLEkCcBrvReKSq6E5w4QbBLrDXfGjiKU,13154
|
|
386
387
|
dstack/_internal/server/services/backends/handlers.py,sha256=j-MhBxrpdepoDG7f2tApjFnE23RVO5I15-hxHyOWnew,3251
|
|
387
388
|
dstack/_internal/server/services/encryption/__init__.py,sha256=3kCw_cxC3-Un1OIofdW5Gqsm0ZCXXTlGz09cULBx_uc,3155
|
|
@@ -389,7 +390,7 @@ dstack/_internal/server/services/encryption/keys/__init__.py,sha256=47DEQpj8HBSa
|
|
|
389
390
|
dstack/_internal/server/services/encryption/keys/aes.py,sha256=2He1p2_Rg6hnCeLIGJo-Yfdsij7so_338oY49RXuL3Q,2276
|
|
390
391
|
dstack/_internal/server/services/encryption/keys/base.py,sha256=mqumJiidoexUPoqxhQG6J_SpC1WGYwkdjKm1MUWnXo8,352
|
|
391
392
|
dstack/_internal/server/services/encryption/keys/identity.py,sha256=ryb_YSV6u4c7W1OsVfEpzJvZCrR4zZYlzLw_GpjpD2Q,741
|
|
392
|
-
dstack/_internal/server/services/gateways/__init__.py,sha256=
|
|
393
|
+
dstack/_internal/server/services/gateways/__init__.py,sha256=Up8uFsEQDBE0yOXn7n5o7Q8MkY0XJfbMWViMFd2EIL4,21530
|
|
393
394
|
dstack/_internal/server/services/gateways/client.py,sha256=XIJX3fGBbZ_AG8qZMTSE8KAB_ojq5YJFa0OXoD_dofg,7493
|
|
394
395
|
dstack/_internal/server/services/gateways/connection.py,sha256=ot3lV85XdmCT45vBWeyj57nLPcLPNm316zu3jMyeWjA,5625
|
|
395
396
|
dstack/_internal/server/services/gateways/pool.py,sha256=0LclTl1tyx-doS78LeaAKjr-SMp98zuwh5f9s06JSd0,1914
|
|
@@ -535,7 +536,7 @@ dstack/_internal/server/utils/common.py,sha256=PbjXtqYy1taKXpyG5ys8cIrz9MXqc9dBA
|
|
|
535
536
|
dstack/_internal/server/utils/logging.py,sha256=bxUS2uWG5snypNRfL0d5sMLCDytyOZac81PSQlb7_rs,1907
|
|
536
537
|
dstack/_internal/server/utils/routers.py,sha256=OzL9Oxy-1no7Txk1r-Pvf28l3S25CYJlyAscYY345Xg,4729
|
|
537
538
|
dstack/_internal/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
538
|
-
dstack/_internal/utils/common.py,sha256=
|
|
539
|
+
dstack/_internal/utils/common.py,sha256=1Ig2D3XUpYfiDHAsqC3JRKwx8PsWp1sWLRVHOlEKwwU,8827
|
|
539
540
|
dstack/_internal/utils/crypto.py,sha256=2RTSyzePuwwqc1X2HO6lwcSFyZ2kujnqluoICQ2DLJQ,1462
|
|
540
541
|
dstack/_internal/utils/dxf.py,sha256=wguK9s6-69kqSDZkxd1kFEr6VlH5ixvFRJxizyOuJ8I,3229
|
|
541
542
|
dstack/_internal/utils/env.py,sha256=HRbIspHpKHh05fMZeV23-hrZoV6vVMuniefD08u6ey0,357
|
|
@@ -558,7 +559,7 @@ dstack/api/_public/backends.py,sha256=w_cIUVU3L1tN8VoPH6-ltq-oJewlfXWTiEFXfDvm-1
|
|
|
558
559
|
dstack/api/_public/repos.py,sha256=2ufUtxY8WyPci9tCGYzsnyIFTXNJfTuPzDYvpTydZ6k,6133
|
|
559
560
|
dstack/api/_public/runs.py,sha256=tILFldJb4EKMYYBMr5rUiD5aJGIPB4-eLXzwwcqPbVg,27828
|
|
560
561
|
dstack/api/huggingface/__init__.py,sha256=oIrEij3wttLZ1yrywEGvCMd6zswMQrX5pPjrqdSi0UA,2201
|
|
561
|
-
dstack/api/server/__init__.py,sha256=
|
|
562
|
+
dstack/api/server/__init__.py,sha256=Zyl1M51tifn4pB150yIsh39N96qUgMjg5XplcElHDxg,6097
|
|
562
563
|
dstack/api/server/_backends.py,sha256=tSvJ4j-yp-S-4IYo7pKHluDaSsx6Xbwo08Ff6Do85fo,1639
|
|
563
564
|
dstack/api/server/_fleets.py,sha256=CMxBH49WF18urYx5kj4-B0D5a26QbaO2uDr5Xg-HH0M,4480
|
|
564
565
|
dstack/api/server/_gateways.py,sha256=FAjSi1l6dj1H0mHuFwcCH-QbRxAxVzPI8xE2cmcvWqo,2688
|
|
@@ -573,8 +574,12 @@ dstack/api/server/_users.py,sha256=XzhgGKc5Tsr0-xkz3T6rGyWZ1tO7aYNhLux2eE7dAoY,1
|
|
|
573
574
|
dstack/api/server/_volumes.py,sha256=xxOt8o5G-bhMh6wSvF4BDFNoqVEhlM4BXQr2KvX0pN0,1937
|
|
574
575
|
dstack/api/server/utils.py,sha256=i1KX4CNXVeDj9CnytdzsJz0bxjvvfLRTb7xw8oqtEtQ,1040
|
|
575
576
|
dstack/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
576
|
-
dstack
|
|
577
|
-
dstack
|
|
578
|
-
dstack
|
|
579
|
-
dstack
|
|
580
|
-
dstack-0.19.
|
|
577
|
+
dstack/plugins/__init__.py,sha256=buT1pcyORLgVbl89ATkRWJPhvejriVz7sNBjvuZRCRE,403
|
|
578
|
+
dstack/plugins/_base.py,sha256=-etiB-EozaJCg2wtmONfj8ic-K03qXvXyl_TIDp-kNE,2662
|
|
579
|
+
dstack/plugins/_models.py,sha256=1Gw--mDQ1_0FFr9Zur9LE8UbMoWESUpTdHHt12AyIZo,341
|
|
580
|
+
dstack/plugins/_utils.py,sha256=FqeWYb7zOrgZkO9Bd8caL5I81_TUEsysIzvxsULrmzk,392
|
|
581
|
+
dstack-0.19.7.dist-info/METADATA,sha256=g67K4f_PhyA2NrwUmQ-SG1cb45HpHMoQUtd5YQwZ3d4,20042
|
|
582
|
+
dstack-0.19.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
583
|
+
dstack-0.19.7.dist-info/entry_points.txt,sha256=GnLrMS8hx3rWAySQjA7tPNhtixV6a-brRkmal1PKoHc,58
|
|
584
|
+
dstack-0.19.7.dist-info/licenses/LICENSE.md,sha256=qDABaRGjSKVOib1U8viw2P_96sIK7Puo426784oD9f8,15976
|
|
585
|
+
dstack-0.19.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|