dstack 0.19.1__py3-none-any.whl → 0.19.3__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.
- dstack/_internal/cli/commands/metrics.py +138 -0
- dstack/_internal/cli/commands/stats.py +5 -119
- dstack/_internal/cli/main.py +2 -0
- dstack/_internal/cli/services/profile.py +9 -0
- dstack/_internal/core/backends/aws/configurator.py +1 -0
- dstack/_internal/core/backends/base/compute.py +4 -1
- dstack/_internal/core/backends/base/models.py +7 -7
- dstack/_internal/core/backends/configurators.py +9 -0
- dstack/_internal/core/backends/cudo/compute.py +2 -0
- dstack/_internal/core/backends/cudo/configurator.py +0 -13
- dstack/_internal/core/backends/datacrunch/compute.py +118 -32
- dstack/_internal/core/backends/datacrunch/configurator.py +16 -11
- dstack/_internal/core/backends/gcp/compute.py +140 -26
- dstack/_internal/core/backends/gcp/configurator.py +2 -0
- dstack/_internal/core/backends/gcp/features/__init__.py +0 -0
- dstack/_internal/core/backends/gcp/features/tcpx.py +34 -0
- dstack/_internal/core/backends/gcp/models.py +13 -1
- dstack/_internal/core/backends/gcp/resources.py +64 -27
- dstack/_internal/core/backends/lambdalabs/compute.py +2 -4
- dstack/_internal/core/backends/lambdalabs/configurator.py +0 -21
- dstack/_internal/core/backends/models.py +8 -0
- dstack/_internal/core/backends/nebius/__init__.py +0 -0
- dstack/_internal/core/backends/nebius/backend.py +16 -0
- dstack/_internal/core/backends/nebius/compute.py +272 -0
- dstack/_internal/core/backends/nebius/configurator.py +74 -0
- dstack/_internal/core/backends/nebius/models.py +108 -0
- dstack/_internal/core/backends/nebius/resources.py +240 -0
- dstack/_internal/core/backends/tensordock/api_client.py +5 -4
- dstack/_internal/core/backends/tensordock/compute.py +2 -15
- dstack/_internal/core/errors.py +14 -0
- dstack/_internal/core/models/backends/base.py +2 -0
- dstack/_internal/core/models/profiles.py +3 -0
- dstack/_internal/proxy/lib/schemas/model_proxy.py +3 -3
- dstack/_internal/server/background/tasks/process_instances.py +12 -7
- dstack/_internal/server/background/tasks/process_running_jobs.py +20 -0
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +3 -2
- dstack/_internal/server/routers/prometheus.py +5 -0
- dstack/_internal/server/security/permissions.py +19 -1
- dstack/_internal/server/services/instances.py +14 -6
- dstack/_internal/server/services/jobs/__init__.py +3 -3
- dstack/_internal/server/services/offers.py +4 -2
- dstack/_internal/server/services/runs.py +0 -2
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/{main-da9f8c06a69c20dac23e.css → main-8f9c66f404e9c7e7e020.css} +1 -1
- dstack/_internal/server/statics/{main-4a0fe83e84574654e397.js → main-e190de603dc1e9f485ec.js} +7306 -149
- dstack/_internal/server/statics/{main-4a0fe83e84574654e397.js.map → main-e190de603dc1e9f485ec.js.map} +1 -1
- dstack/_internal/utils/common.py +8 -2
- dstack/_internal/utils/event_loop.py +30 -0
- dstack/_internal/utils/ignore.py +2 -0
- dstack/api/server/_fleets.py +3 -5
- dstack/api/server/_runs.py +6 -7
- dstack/version.py +1 -1
- {dstack-0.19.1.dist-info → dstack-0.19.3.dist-info}/METADATA +27 -11
- {dstack-0.19.1.dist-info → dstack-0.19.3.dist-info}/RECORD +67 -57
- tests/_internal/core/backends/datacrunch/test_configurator.py +6 -2
- tests/_internal/server/background/tasks/test_process_instances.py +4 -2
- tests/_internal/server/background/tasks/test_process_submitted_jobs.py +29 -0
- tests/_internal/server/routers/test_backends.py +116 -0
- tests/_internal/server/routers/test_fleets.py +2 -0
- tests/_internal/server/routers/test_prometheus.py +21 -0
- tests/_internal/server/routers/test_runs.py +4 -0
- tests/_internal/utils/test_common.py +16 -1
- tests/_internal/utils/test_event_loop.py +18 -0
- dstack/_internal/core/backends/datacrunch/api_client.py +0 -77
- {dstack-0.19.1.dist-info → dstack-0.19.3.dist-info}/LICENSE.md +0 -0
- {dstack-0.19.1.dist-info → dstack-0.19.3.dist-info}/WHEEL +0 -0
- {dstack-0.19.1.dist-info → dstack-0.19.3.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.1.dist-info → dstack-0.19.3.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@ from dstack._internal.core.models.instances import InstanceType
|
|
|
8
8
|
from dstack._internal.utils.logging import get_logger
|
|
9
9
|
|
|
10
10
|
logger = get_logger(__name__)
|
|
11
|
-
REQUEST_TIMEOUT =
|
|
11
|
+
REQUEST_TIMEOUT = 20
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class TensorDockAPIClient:
|
|
@@ -80,7 +80,7 @@ class TensorDockAPIClient:
|
|
|
80
80
|
data["password"] = form["password"]
|
|
81
81
|
return data
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def delete_single_if_exists(self, instance_id: str):
|
|
84
84
|
logger.debug("Deleting instance %s", instance_id)
|
|
85
85
|
resp = self.s.post(
|
|
86
86
|
self._url("/client/delete/single"),
|
|
@@ -91,10 +91,11 @@ class TensorDockAPIClient:
|
|
|
91
91
|
},
|
|
92
92
|
timeout=REQUEST_TIMEOUT,
|
|
93
93
|
)
|
|
94
|
-
resp.raise_for_status()
|
|
95
94
|
try:
|
|
96
95
|
data = resp.json()
|
|
97
|
-
if
|
|
96
|
+
if "already terminated" in data.get("error", ""):
|
|
97
|
+
return
|
|
98
|
+
if not data.get("success"):
|
|
98
99
|
raise BackendError(data)
|
|
99
100
|
except ValueError: # json parsing error
|
|
100
101
|
raise BackendError(resp.text)
|
|
@@ -12,7 +12,7 @@ from dstack._internal.core.backends.base.compute import (
|
|
|
12
12
|
from dstack._internal.core.backends.base.offers import get_catalog_offers
|
|
13
13
|
from dstack._internal.core.backends.tensordock.api_client import TensorDockAPIClient
|
|
14
14
|
from dstack._internal.core.backends.tensordock.models import TensorDockConfig
|
|
15
|
-
from dstack._internal.core.errors import
|
|
15
|
+
from dstack._internal.core.errors import NoCapacityError
|
|
16
16
|
from dstack._internal.core.models.backends.base import BackendType
|
|
17
17
|
from dstack._internal.core.models.instances import (
|
|
18
18
|
InstanceAvailability,
|
|
@@ -117,17 +117,4 @@ class TensorDockCompute(
|
|
|
117
117
|
def terminate_instance(
|
|
118
118
|
self, instance_id: str, region: str, backend_data: Optional[str] = None
|
|
119
119
|
):
|
|
120
|
-
|
|
121
|
-
self.api_client.delete_single(instance_id)
|
|
122
|
-
except requests.HTTPError as e:
|
|
123
|
-
logger.error(
|
|
124
|
-
"An HTTP error occurred when trying to terminate TensorDock instance %s: %s",
|
|
125
|
-
instance_id,
|
|
126
|
-
e,
|
|
127
|
-
)
|
|
128
|
-
except BackendError as e:
|
|
129
|
-
logger.error(
|
|
130
|
-
"TensorDock returned an error when trying to terminate instance %s: %s",
|
|
131
|
-
instance_id,
|
|
132
|
-
e,
|
|
133
|
-
)
|
|
120
|
+
self.api_client.delete_single_if_exists(instance_id)
|
dstack/_internal/core/errors.py
CHANGED
|
@@ -102,6 +102,20 @@ class PlacementGroupInUseError(ComputeError):
|
|
|
102
102
|
pass
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
class NotYetTerminated(ComputeError):
|
|
106
|
+
"""
|
|
107
|
+
Used by Compute.terminate_instance to signal that instance termination is not complete
|
|
108
|
+
and the method should be called again after some time to continue termination.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self, details: str) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Args:
|
|
114
|
+
details: some details about the termination status
|
|
115
|
+
"""
|
|
116
|
+
return super().__init__(details)
|
|
117
|
+
|
|
118
|
+
|
|
105
119
|
class CLIError(DstackError):
|
|
106
120
|
pass
|
|
107
121
|
|
|
@@ -12,6 +12,7 @@ class BackendType(str, enum.Enum):
|
|
|
12
12
|
DATACRUNCH (BackendType): DataCrunch
|
|
13
13
|
KUBERNETES (BackendType): Kubernetes
|
|
14
14
|
LAMBDA (BackendType): Lambda Cloud
|
|
15
|
+
NEBIUS (BackendType): Nebius AI Cloud
|
|
15
16
|
OCI (BackendType): Oracle Cloud Infrastructure
|
|
16
17
|
RUNPOD (BackendType): Runpod Cloud
|
|
17
18
|
TENSORDOCK (BackendType): TensorDock Marketplace
|
|
@@ -29,6 +30,7 @@ class BackendType(str, enum.Enum):
|
|
|
29
30
|
LAMBDA = "lambda"
|
|
30
31
|
LOCAL = "local"
|
|
31
32
|
REMOTE = "remote" # TODO: replace for LOCAL
|
|
33
|
+
NEBIUS = "nebius"
|
|
32
34
|
OCI = "oci"
|
|
33
35
|
RUNPOD = "runpod"
|
|
34
36
|
TENSORDOCK = "tensordock"
|
|
@@ -240,6 +240,9 @@ class ProfileParams(CoreModel):
|
|
|
240
240
|
Optional[UtilizationPolicy],
|
|
241
241
|
Field(description="Run termination policy based on utilization"),
|
|
242
242
|
] = None
|
|
243
|
+
fleets: Annotated[
|
|
244
|
+
Optional[list[str]], Field(description="The fleets considered for reuse")
|
|
245
|
+
] = None
|
|
243
246
|
|
|
244
247
|
# Deprecated and unused. Left for compatibility with 0.18 clients.
|
|
245
248
|
pool_name: Annotated[Optional[str], Field(exclude=True)] = None
|
|
@@ -57,11 +57,11 @@ class ChatCompletionsResponse(CoreModel):
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
class ChatCompletionsChunk(CoreModel):
|
|
60
|
-
id: str
|
|
60
|
+
id: Optional[str] = None
|
|
61
61
|
choices: List[ChatCompletionsChunkChoice]
|
|
62
|
-
created: int
|
|
62
|
+
created: Optional[int] = None
|
|
63
63
|
model: str
|
|
64
|
-
system_fingerprint: str = ""
|
|
64
|
+
system_fingerprint: Optional[str] = ""
|
|
65
65
|
object: Literal["chat.completion.chunk"] = "chat.completion.chunk"
|
|
66
66
|
|
|
67
67
|
|
|
@@ -39,7 +39,7 @@ from dstack._internal.core.backends.remote.provisioning import (
|
|
|
39
39
|
from dstack._internal.core.consts import DSTACK_SHIM_HTTP_PORT
|
|
40
40
|
|
|
41
41
|
# FIXME: ProvisioningError is a subclass of ComputeError and should not be used outside of Compute
|
|
42
|
-
from dstack._internal.core.errors import BackendError, ProvisioningError
|
|
42
|
+
from dstack._internal.core.errors import BackendError, NotYetTerminated, ProvisioningError
|
|
43
43
|
from dstack._internal.core.models.backends.base import BackendType
|
|
44
44
|
from dstack._internal.core.models.fleets import InstanceGroupPlacement
|
|
45
45
|
from dstack._internal.core.models.instances import (
|
|
@@ -846,12 +846,17 @@ async def _terminate(instance: InstanceModel) -> None:
|
|
|
846
846
|
instance.first_termination_retry_at = get_current_datetime()
|
|
847
847
|
instance.last_termination_retry_at = get_current_datetime()
|
|
848
848
|
if _next_termination_retry_at(instance) < _get_termination_deadline(instance):
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
849
|
+
if isinstance(e, NotYetTerminated):
|
|
850
|
+
logger.debug(
|
|
851
|
+
"Instance %s termination in progress: %s", instance.name, e
|
|
852
|
+
)
|
|
853
|
+
else:
|
|
854
|
+
logger.warning(
|
|
855
|
+
"Failed to terminate instance %s. Will retry. Error: %r",
|
|
856
|
+
instance.name,
|
|
857
|
+
e,
|
|
858
|
+
exc_info=not isinstance(e, BackendError),
|
|
859
|
+
)
|
|
855
860
|
return
|
|
856
861
|
logger.error(
|
|
857
862
|
"Failed all attempts to terminate instance %s."
|
|
@@ -434,6 +434,10 @@ def _process_provisioning_with_shim(
|
|
|
434
434
|
for volume, volume_mount in zip(volumes, volume_mounts):
|
|
435
435
|
volume_mount.name = volume.name
|
|
436
436
|
|
|
437
|
+
instance_mounts += _get_instance_specific_mounts(
|
|
438
|
+
job_provisioning_data.backend, job_provisioning_data.instance_type.name
|
|
439
|
+
)
|
|
440
|
+
|
|
437
441
|
container_user = "root"
|
|
438
442
|
|
|
439
443
|
job_runtime_data = get_job_runtime_data(job_model)
|
|
@@ -825,3 +829,19 @@ def _submit_job_to_runner(
|
|
|
825
829
|
# do not log here, because the runner will send a new status
|
|
826
830
|
|
|
827
831
|
return True
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _get_instance_specific_mounts(
|
|
835
|
+
backend_type: BackendType, instance_type_name: str
|
|
836
|
+
) -> List[InstanceMountPoint]:
|
|
837
|
+
if backend_type == BackendType.GCP and instance_type_name == "a3-megagpu-8g":
|
|
838
|
+
return [
|
|
839
|
+
InstanceMountPoint(
|
|
840
|
+
instance_path="/dev/aperture_devices", path="/dev/aperture_devices"
|
|
841
|
+
),
|
|
842
|
+
InstanceMountPoint(instance_path="/var/lib/tcpxo/lib64", path="/var/lib/tcpxo/lib64"),
|
|
843
|
+
InstanceMountPoint(
|
|
844
|
+
instance_path="/var/lib/fastrak/lib64", path="/var/lib/fastrak/lib64"
|
|
845
|
+
),
|
|
846
|
+
]
|
|
847
|
+
return []
|
|
@@ -212,6 +212,7 @@ async def _process_submitted_job(session: AsyncSession, job_model: JobModel):
|
|
|
212
212
|
InstanceModel.deleted == False,
|
|
213
213
|
InstanceModel.total_blocks > InstanceModel.busy_blocks,
|
|
214
214
|
)
|
|
215
|
+
.options(joinedload(InstanceModel.fleet))
|
|
215
216
|
.execution_options(populate_existing=True)
|
|
216
217
|
)
|
|
217
218
|
pool_instances = list(res.unique().scalars().all())
|
|
@@ -612,7 +613,7 @@ def _get_offer_mount_point_volume(
|
|
|
612
613
|
for volume in volumes:
|
|
613
614
|
if (
|
|
614
615
|
volume.configuration.backend != offer.backend
|
|
615
|
-
or volume.configuration.region != offer.region
|
|
616
|
+
or volume.configuration.region.lower() != offer.region.lower()
|
|
616
617
|
):
|
|
617
618
|
continue
|
|
618
619
|
return volume
|
|
@@ -640,7 +641,7 @@ async def _attach_volumes(
|
|
|
640
641
|
try:
|
|
641
642
|
if (
|
|
642
643
|
job_provisioning_data.get_base_backend() != volume.configuration.backend
|
|
643
|
-
or job_provisioning_data.region != volume.configuration.region
|
|
644
|
+
or job_provisioning_data.region.lower() != volume.configuration.region.lower()
|
|
644
645
|
):
|
|
645
646
|
continue
|
|
646
647
|
if volume.provisioning_data is not None and volume.provisioning_data.attachable:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from typing import Annotated
|
|
2
3
|
|
|
3
4
|
from fastapi import APIRouter, Depends
|
|
@@ -6,12 +7,16 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
6
7
|
|
|
7
8
|
from dstack._internal.server import settings
|
|
8
9
|
from dstack._internal.server.db import get_session
|
|
10
|
+
from dstack._internal.server.security.permissions import OptionalServiceAccount
|
|
9
11
|
from dstack._internal.server.services import prometheus
|
|
10
12
|
from dstack._internal.server.utils.routers import error_not_found
|
|
11
13
|
|
|
14
|
+
_auth = OptionalServiceAccount(os.getenv("DSTACK_PROMETHEUS_AUTH_TOKEN"))
|
|
15
|
+
|
|
12
16
|
router = APIRouter(
|
|
13
17
|
tags=["prometheus"],
|
|
14
18
|
default_response_class=PlainTextResponse,
|
|
19
|
+
dependencies=[Depends(_auth)],
|
|
15
20
|
)
|
|
16
21
|
|
|
17
22
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Tuple
|
|
1
|
+
from typing import Annotated, Optional, Tuple
|
|
2
2
|
|
|
3
3
|
from fastapi import Depends, HTTPException, Security
|
|
4
4
|
from fastapi.security import HTTPBearer
|
|
@@ -99,6 +99,24 @@ class ProjectMember:
|
|
|
99
99
|
return await get_project_member(session, project_name, token.credentials)
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
class OptionalServiceAccount:
|
|
103
|
+
def __init__(self, token: Optional[str]) -> None:
|
|
104
|
+
self._token = token
|
|
105
|
+
|
|
106
|
+
async def __call__(
|
|
107
|
+
self,
|
|
108
|
+
token: Annotated[
|
|
109
|
+
Optional[HTTPAuthorizationCredentials], Security(HTTPBearer(auto_error=False))
|
|
110
|
+
],
|
|
111
|
+
) -> None:
|
|
112
|
+
if self._token is None:
|
|
113
|
+
return
|
|
114
|
+
if token is None:
|
|
115
|
+
raise error_forbidden()
|
|
116
|
+
if token.credentials != self._token:
|
|
117
|
+
raise error_invalid_token()
|
|
118
|
+
|
|
119
|
+
|
|
102
120
|
async def get_project_member(
|
|
103
121
|
session: AsyncSession, project_name: str, token: str
|
|
104
122
|
) -> Tuple[UserModel, ProjectModel]:
|
|
@@ -176,23 +176,29 @@ def filter_pool_instances(
|
|
|
176
176
|
regions = [master_job_provisioning_data.region]
|
|
177
177
|
regions = [r for r in regions if r == master_job_provisioning_data.region]
|
|
178
178
|
|
|
179
|
+
if regions is not None:
|
|
180
|
+
regions = [r.lower() for r in regions]
|
|
181
|
+
instance_types = profile.instance_types
|
|
182
|
+
if instance_types is not None:
|
|
183
|
+
instance_types = [i.lower() for i in instance_types]
|
|
184
|
+
|
|
179
185
|
for instance in pool_instances:
|
|
180
186
|
if fleet_model is not None and instance.fleet_id != fleet_model.id:
|
|
181
187
|
continue
|
|
182
188
|
if instance.unreachable:
|
|
183
189
|
continue
|
|
190
|
+
fleet = instance.fleet
|
|
191
|
+
if profile.fleets is not None and (fleet is None or fleet.name not in profile.fleets):
|
|
192
|
+
continue
|
|
184
193
|
if status is not None and instance.status != status:
|
|
185
194
|
continue
|
|
186
195
|
jpd = get_instance_provisioning_data(instance)
|
|
187
196
|
if jpd is not None:
|
|
188
197
|
if backend_types is not None and jpd.get_base_backend() not in backend_types:
|
|
189
198
|
continue
|
|
190
|
-
if regions is not None and jpd.region not in regions:
|
|
199
|
+
if regions is not None and jpd.region.lower() not in regions:
|
|
191
200
|
continue
|
|
192
|
-
if (
|
|
193
|
-
profile.instance_types is not None
|
|
194
|
-
and jpd.instance_type.name not in profile.instance_types
|
|
195
|
-
):
|
|
201
|
+
if instance_types is not None and jpd.instance_type.name.lower() not in instance_types:
|
|
196
202
|
continue
|
|
197
203
|
if (
|
|
198
204
|
jpd.availability_zone is not None
|
|
@@ -268,10 +274,12 @@ async def get_pool_instances(
|
|
|
268
274
|
project: ProjectModel,
|
|
269
275
|
) -> List[InstanceModel]:
|
|
270
276
|
res = await session.execute(
|
|
271
|
-
select(InstanceModel)
|
|
277
|
+
select(InstanceModel)
|
|
278
|
+
.where(
|
|
272
279
|
InstanceModel.project_id == project.id,
|
|
273
280
|
InstanceModel.deleted == False,
|
|
274
281
|
)
|
|
282
|
+
.options(joinedload(InstanceModel.fleet))
|
|
275
283
|
)
|
|
276
284
|
instance_models = list(res.unique().scalars().all())
|
|
277
285
|
return instance_models
|
|
@@ -668,15 +668,15 @@ def _get_job_mount_point_attached_volume(
|
|
|
668
668
|
for volume in volumes:
|
|
669
669
|
if (
|
|
670
670
|
volume.configuration.backend != job_provisioning_data.get_base_backend()
|
|
671
|
-
or volume.configuration.region != job_provisioning_data.region
|
|
671
|
+
or volume.configuration.region.lower() != job_provisioning_data.region.lower()
|
|
672
672
|
):
|
|
673
673
|
continue
|
|
674
674
|
if (
|
|
675
675
|
volume.provisioning_data is not None
|
|
676
676
|
and volume.provisioning_data.availability_zone is not None
|
|
677
677
|
and job_provisioning_data.availability_zone is not None
|
|
678
|
-
and volume.provisioning_data.availability_zone
|
|
679
|
-
!= job_provisioning_data.availability_zone
|
|
678
|
+
and volume.provisioning_data.availability_zone.lower()
|
|
679
|
+
!= job_provisioning_data.availability_zone.lower()
|
|
680
680
|
):
|
|
681
681
|
continue
|
|
682
682
|
return volume
|
|
@@ -101,7 +101,8 @@ async def get_offers_by_requirements(
|
|
|
101
101
|
offers = [(b, o) for b, o in offers if o.backend in backend_types]
|
|
102
102
|
|
|
103
103
|
if regions is not None:
|
|
104
|
-
|
|
104
|
+
regions = [r.lower() for r in regions]
|
|
105
|
+
offers = [(b, o) for b, o in offers if o.region.lower() in regions]
|
|
105
106
|
|
|
106
107
|
if availability_zones is not None:
|
|
107
108
|
new_offers = []
|
|
@@ -116,7 +117,8 @@ async def get_offers_by_requirements(
|
|
|
116
117
|
offers = new_offers
|
|
117
118
|
|
|
118
119
|
if profile.instance_types is not None:
|
|
119
|
-
|
|
120
|
+
instance_types = [i.lower() for i in profile.instance_types]
|
|
121
|
+
offers = [(b, o) for b, o in offers if o.instance.name.lower() in instance_types]
|
|
120
122
|
|
|
121
123
|
if blocks == 1:
|
|
122
124
|
return offers
|
|
@@ -831,8 +831,6 @@ def _validate_run_spec_and_set_defaults(run_spec: RunSpec):
|
|
|
831
831
|
for mount_point in run_spec.configuration.volumes:
|
|
832
832
|
if not is_valid_docker_volume_target(mount_point.path):
|
|
833
833
|
raise ServerClientError(f"Invalid volume mount path: {mount_point.path}")
|
|
834
|
-
if mount_point.path.startswith("/workflow"):
|
|
835
|
-
raise ServerClientError("Mounting volumes inside /workflow is not supported")
|
|
836
834
|
if run_spec.repo_id is None and run_spec.repo_data is not None:
|
|
837
835
|
raise ServerClientError("repo_data must not be set if repo_id is not set")
|
|
838
836
|
if run_spec.repo_id is not None and run_spec.repo_data is None:
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>dstack</title><meta name="description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
2
2
|
"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"><meta name="og:title" content="dstack"><meta name="og:type" content="article"><meta name="og:image" content="/splash_thumbnail.png"><meta name="og:description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
3
|
-
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-
|
|
3
|
+
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-e190de603dc1e9f485ec.js"></script><link href="/main-8f9c66f404e9c7e7e020.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div></body></html>
|