dstack 0.18.39__py3-none-any.whl → 0.18.40rc1__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 +1 -1
- dstack/_internal/cli/services/configurators/gateway.py +10 -1
- dstack/_internal/cli/utils/common.py +1 -2
- dstack/_internal/core/models/configurations.py +21 -0
- dstack/_internal/core/models/runs.py +1 -0
- dstack/_internal/core/models/volumes.py +9 -0
- dstack/_internal/core/services/logs.py +4 -1
- dstack/_internal/core/services/ssh/attach.py +6 -5
- dstack/_internal/proxy/lib/models.py +1 -0
- dstack/_internal/proxy/lib/testing/common.py +2 -0
- dstack/_internal/server/app.py +7 -3
- dstack/_internal/server/background/tasks/process_submitted_jobs.py +2 -2
- dstack/_internal/server/schemas/runner.py +1 -0
- dstack/_internal/server/services/fleets.py +1 -1
- dstack/_internal/server/services/jobs/configurators/base.py +10 -0
- dstack/_internal/server/services/jobs/configurators/dev.py +3 -0
- dstack/_internal/server/services/jobs/configurators/service.py +3 -0
- dstack/_internal/server/services/jobs/configurators/task.py +3 -0
- dstack/_internal/server/services/proxy/repo.py +1 -0
- dstack/_internal/server/services/proxy/services/service_proxy.py +4 -0
- dstack/_internal/server/services/runs.py +5 -4
- dstack/api/_public/runs.py +8 -0
- dstack/api/server/__init__.py +19 -3
- dstack/api/server/_runs.py +28 -8
- dstack/version.py +1 -1
- {dstack-0.18.39.dist-info → dstack-0.18.40rc1.dist-info}/METADATA +6 -7
- {dstack-0.18.39.dist-info → dstack-0.18.40rc1.dist-info}/RECORD +36 -36
- tests/_internal/core/services/test_logs.py +16 -6
- tests/_internal/server/background/tasks/test_process_submitted_jobs.py +74 -1
- tests/_internal/server/routers/test_runs.py +4 -0
- tests/_internal/server/services/proxy/routers/test_service_proxy.py +39 -0
- tests/_internal/server/services/runner/test_client.py +6 -2
- {dstack-0.18.39.dist-info → dstack-0.18.40rc1.dist-info}/LICENSE.md +0 -0
- {dstack-0.18.39.dist-info → dstack-0.18.40rc1.dist-info}/WHEEL +0 -0
- {dstack-0.18.39.dist-info → dstack-0.18.40rc1.dist-info}/entry_points.txt +0 -0
- {dstack-0.18.39.dist-info → dstack-0.18.40rc1.dist-info}/top_level.txt +0 -0
|
@@ -22,6 +22,7 @@ from dstack._internal.core.models.gateways import (
|
|
|
22
22
|
GatewayStatus,
|
|
23
23
|
)
|
|
24
24
|
from dstack._internal.core.models.repos.base import Repo
|
|
25
|
+
from dstack._internal.core.services.diff import diff_models
|
|
25
26
|
from dstack._internal.utils.common import local_time
|
|
26
27
|
from dstack.api._public import Client
|
|
27
28
|
|
|
@@ -56,7 +57,15 @@ class GatewayConfigurator(BaseApplyConfigurator):
|
|
|
56
57
|
confirm_message += "Create the gateway?"
|
|
57
58
|
else:
|
|
58
59
|
action_message += f"Found gateway [code]{plan.spec.configuration.name}[/]."
|
|
59
|
-
|
|
60
|
+
diff = diff_models(
|
|
61
|
+
plan.spec.configuration,
|
|
62
|
+
plan.current_resource.configuration,
|
|
63
|
+
)
|
|
64
|
+
changed_fields = list(diff.keys())
|
|
65
|
+
if (
|
|
66
|
+
plan.current_resource.configuration == plan.spec.configuration
|
|
67
|
+
or changed_fields == ["default"]
|
|
68
|
+
):
|
|
60
69
|
if command_args.yes and not command_args.force:
|
|
61
70
|
# --force is required only with --yes,
|
|
62
71
|
# otherwise we may ask for force apply interactively.
|
|
@@ -25,8 +25,7 @@ LIVE_TABLE_REFRESH_RATE_PER_SEC = 1
|
|
|
25
25
|
LIVE_TABLE_PROVISION_INTERVAL_SECS = 2
|
|
26
26
|
NO_OFFERS_WARNING = (
|
|
27
27
|
"[warning]"
|
|
28
|
-
"
|
|
29
|
-
" Please check the requirements table above or visit the troubleshooting guide:"
|
|
28
|
+
"No matching instance offers available. Possible reasons:"
|
|
30
29
|
" https://dstack.ai/docs/guides/troubleshooting/#no-offers"
|
|
31
30
|
"[/]\n"
|
|
32
31
|
)
|
|
@@ -21,6 +21,7 @@ from dstack._internal.core.models.volumes import MountPoint, VolumeConfiguration
|
|
|
21
21
|
CommandsList = List[str]
|
|
22
22
|
ValidPort = conint(gt=0, le=65536)
|
|
23
23
|
SERVICE_HTTPS_DEFAULT = True
|
|
24
|
+
STRIP_PREFIX_DEFAULT = True
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class RunConfigurationType(str, Enum):
|
|
@@ -130,6 +131,16 @@ class BaseRunConfiguration(CoreModel):
|
|
|
130
131
|
description="Use image with NVIDIA CUDA Compiler (NVCC) included. Mutually exclusive with `image`"
|
|
131
132
|
),
|
|
132
133
|
]
|
|
134
|
+
single_branch: Annotated[
|
|
135
|
+
Optional[bool],
|
|
136
|
+
Field(
|
|
137
|
+
description=(
|
|
138
|
+
"Whether to clone and track only the current branch or all remote branches."
|
|
139
|
+
" Relevant only when using remote Git repos."
|
|
140
|
+
" Defaults to `false` for dev environments and to `true` for tasks and services"
|
|
141
|
+
)
|
|
142
|
+
),
|
|
143
|
+
] = None
|
|
133
144
|
env: Annotated[
|
|
134
145
|
Env,
|
|
135
146
|
Field(description="The mapping or the list of environment variables"),
|
|
@@ -236,6 +247,16 @@ class ServiceConfigurationParams(CoreModel):
|
|
|
236
247
|
),
|
|
237
248
|
),
|
|
238
249
|
] = None
|
|
250
|
+
strip_prefix: Annotated[
|
|
251
|
+
bool,
|
|
252
|
+
Field(
|
|
253
|
+
description=(
|
|
254
|
+
"Strip the `/proxy/services/<project name>/<run name>/` path prefix"
|
|
255
|
+
" when forwarding requests to the service. Only takes effect"
|
|
256
|
+
" when running the service without a gateway"
|
|
257
|
+
)
|
|
258
|
+
),
|
|
259
|
+
] = STRIP_PREFIX_DEFAULT
|
|
239
260
|
model: Annotated[
|
|
240
261
|
Optional[Union[AnyModel, str]],
|
|
241
262
|
Field(
|
|
@@ -184,6 +184,7 @@ class JobSpec(CoreModel):
|
|
|
184
184
|
home_dir: Optional[str]
|
|
185
185
|
image_name: str
|
|
186
186
|
privileged: bool = False
|
|
187
|
+
single_branch: Optional[bool] = None
|
|
187
188
|
max_duration: Optional[int]
|
|
188
189
|
stop_duration: Optional[int] = None
|
|
189
190
|
registry_auth: Optional[RegistryAuth]
|
|
@@ -136,6 +136,15 @@ class VolumeMountPoint(CoreModel):
|
|
|
136
136
|
class InstanceMountPoint(CoreModel):
|
|
137
137
|
instance_path: Annotated[str, Field(description="The absolute path on the instance (host)")]
|
|
138
138
|
path: Annotated[str, Field(description="The absolute path in the container")]
|
|
139
|
+
optional: Annotated[
|
|
140
|
+
bool,
|
|
141
|
+
Field(
|
|
142
|
+
description=(
|
|
143
|
+
"Allow running without this volume"
|
|
144
|
+
" in backends that do not support instance volumes"
|
|
145
|
+
),
|
|
146
|
+
),
|
|
147
|
+
] = False
|
|
139
148
|
|
|
140
149
|
_validate_instance_path = validator("instance_path", allow_reuse=True)(
|
|
141
150
|
_validate_mount_point_path
|
|
@@ -42,11 +42,14 @@ class URLReplacer:
|
|
|
42
42
|
qs = {k: v[0] for k, v in urllib.parse.parse_qs(url.query).items()}
|
|
43
43
|
if app_spec and app_spec.url_query_params is not None:
|
|
44
44
|
qs.update({k.encode(): v.encode() for k, v in app_spec.url_query_params.items()})
|
|
45
|
+
path = url.path
|
|
46
|
+
if not path.startswith(self.path_prefix.removesuffix(b"/")):
|
|
47
|
+
path = concat_url_path(self.path_prefix, path)
|
|
45
48
|
|
|
46
49
|
url = url._replace(
|
|
47
50
|
scheme=("https" if self.secure else "http").encode(),
|
|
48
51
|
netloc=(self.hostname if omit_port else f"{self.hostname}:{local_port}").encode(),
|
|
49
|
-
path=
|
|
52
|
+
path=path,
|
|
50
53
|
query=urllib.parse.urlencode(qs).encode(),
|
|
51
54
|
)
|
|
52
55
|
return url.geturl()
|
|
@@ -56,6 +56,7 @@ class SSHAttach:
|
|
|
56
56
|
ssh_port: int,
|
|
57
57
|
container_ssh_port: int,
|
|
58
58
|
user: str,
|
|
59
|
+
container_user: str,
|
|
59
60
|
id_rsa_path: PathLike,
|
|
60
61
|
ports_lock: PortsLock,
|
|
61
62
|
run_name: str,
|
|
@@ -74,7 +75,7 @@ class SSHAttach:
|
|
|
74
75
|
self.control_sock_path = FilePath(control_sock_path)
|
|
75
76
|
self.identity_file = FilePath(id_rsa_path)
|
|
76
77
|
self.tunnel = SSHTunnel(
|
|
77
|
-
destination=run_name,
|
|
78
|
+
destination=f"root@{run_name}",
|
|
78
79
|
identity=self.identity_file,
|
|
79
80
|
forwarded_sockets=ports_to_forwarded_sockets(
|
|
80
81
|
ports=self.ports,
|
|
@@ -91,7 +92,7 @@ class SSHAttach:
|
|
|
91
92
|
self.host_config = {
|
|
92
93
|
"HostName": hostname,
|
|
93
94
|
"Port": ssh_port,
|
|
94
|
-
"User": user,
|
|
95
|
+
"User": user if dockerized else container_user,
|
|
95
96
|
"IdentityFile": self.identity_file,
|
|
96
97
|
"IdentitiesOnly": "yes",
|
|
97
98
|
"StrictHostKeyChecking": "no",
|
|
@@ -111,7 +112,7 @@ class SSHAttach:
|
|
|
111
112
|
self.container_config = {
|
|
112
113
|
"HostName": "localhost",
|
|
113
114
|
"Port": container_ssh_port,
|
|
114
|
-
"User":
|
|
115
|
+
"User": container_user,
|
|
115
116
|
"IdentityFile": self.identity_file,
|
|
116
117
|
"IdentitiesOnly": "yes",
|
|
117
118
|
"StrictHostKeyChecking": "no",
|
|
@@ -122,7 +123,7 @@ class SSHAttach:
|
|
|
122
123
|
self.container_config = {
|
|
123
124
|
"HostName": hostname,
|
|
124
125
|
"Port": ssh_port,
|
|
125
|
-
"User":
|
|
126
|
+
"User": container_user,
|
|
126
127
|
"IdentityFile": self.identity_file,
|
|
127
128
|
"IdentitiesOnly": "yes",
|
|
128
129
|
"StrictHostKeyChecking": "no",
|
|
@@ -136,7 +137,7 @@ class SSHAttach:
|
|
|
136
137
|
self.host_config = {
|
|
137
138
|
"HostName": hostname,
|
|
138
139
|
"Port": container_ssh_port,
|
|
139
|
-
"User":
|
|
140
|
+
"User": container_user,
|
|
140
141
|
"IdentityFile": self.identity_file,
|
|
141
142
|
"IdentitiesOnly": "yes",
|
|
142
143
|
"StrictHostKeyChecking": "no",
|
|
@@ -29,6 +29,7 @@ def make_service(
|
|
|
29
29
|
domain: Optional[str] = None,
|
|
30
30
|
https: Optional[bool] = None,
|
|
31
31
|
auth: bool = False,
|
|
32
|
+
strip_prefix: bool = True,
|
|
32
33
|
) -> Service:
|
|
33
34
|
return Service(
|
|
34
35
|
project_name=project_name,
|
|
@@ -37,6 +38,7 @@ def make_service(
|
|
|
37
38
|
https=https,
|
|
38
39
|
auth=auth,
|
|
39
40
|
client_max_body_size=2**20,
|
|
41
|
+
strip_prefix=strip_prefix,
|
|
40
42
|
replicas=(
|
|
41
43
|
Replica(
|
|
42
44
|
id="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
dstack/_internal/server/app.py
CHANGED
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Awaitable, Callable, List
|
|
8
8
|
|
|
9
9
|
import sentry_sdk
|
|
10
|
-
from fastapi import FastAPI, Request, status
|
|
10
|
+
from fastapi import FastAPI, Request, Response, status
|
|
11
11
|
from fastapi.datastructures import URL
|
|
12
12
|
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
|
|
13
13
|
from fastapi.staticfiles import StaticFiles
|
|
@@ -215,10 +215,14 @@ def register_routes(app: FastAPI, ui: bool = True):
|
|
|
215
215
|
@app.middleware("http")
|
|
216
216
|
async def log_request(request: Request, call_next):
|
|
217
217
|
start_time = time.time()
|
|
218
|
-
response = await call_next(request)
|
|
218
|
+
response: Response = await call_next(request)
|
|
219
219
|
process_time = time.time() - start_time
|
|
220
220
|
logger.debug(
|
|
221
|
-
"Processed request %s %s in %s
|
|
221
|
+
"Processed request %s %s in %s. Status: %s",
|
|
222
|
+
request.method,
|
|
223
|
+
request.url,
|
|
224
|
+
f"{process_time:0.6f}s",
|
|
225
|
+
response.status_code,
|
|
222
226
|
)
|
|
223
227
|
return response
|
|
224
228
|
|
|
@@ -64,7 +64,7 @@ from dstack._internal.server.services.pools import (
|
|
|
64
64
|
)
|
|
65
65
|
from dstack._internal.server.services.runs import (
|
|
66
66
|
check_can_attach_run_volumes,
|
|
67
|
-
|
|
67
|
+
check_run_spec_requires_instance_mounts,
|
|
68
68
|
get_offer_volumes,
|
|
69
69
|
get_run_volume_models,
|
|
70
70
|
get_run_volumes,
|
|
@@ -418,7 +418,7 @@ async def _run_job_on_new_instance(
|
|
|
418
418
|
master_job_provisioning_data=master_job_provisioning_data,
|
|
419
419
|
volumes=volumes,
|
|
420
420
|
privileged=job.job_spec.privileged,
|
|
421
|
-
instance_mounts=
|
|
421
|
+
instance_mounts=check_run_spec_requires_instance_mounts(run.run_spec),
|
|
422
422
|
)
|
|
423
423
|
# Limit number of offers tried to prevent long-running processing
|
|
424
424
|
# in case all offers fail.
|
|
@@ -239,7 +239,6 @@ async def get_plan(
|
|
|
239
239
|
user: UserModel,
|
|
240
240
|
spec: FleetSpec,
|
|
241
241
|
) -> FleetPlan:
|
|
242
|
-
# TODO: refactor offers logic into a separate module to avoid depending on runs
|
|
243
242
|
current_fleet: Optional[Fleet] = None
|
|
244
243
|
current_fleet_id: Optional[uuid.UUID] = None
|
|
245
244
|
if spec.configuration.name is not None:
|
|
@@ -259,6 +258,7 @@ async def get_plan(
|
|
|
259
258
|
requirements=_get_fleet_requirements(spec),
|
|
260
259
|
)
|
|
261
260
|
offers = [offer for _, offer in offers_with_backends]
|
|
261
|
+
_remove_fleet_spec_sensitive_info(spec)
|
|
262
262
|
plan = FleetPlan(
|
|
263
263
|
project_name=project.name,
|
|
264
264
|
user=user.name,
|
|
@@ -63,6 +63,10 @@ class JobConfigurator(ABC):
|
|
|
63
63
|
def _shell_commands(self) -> List[str]:
|
|
64
64
|
pass
|
|
65
65
|
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def _default_single_branch(self) -> bool:
|
|
68
|
+
pass
|
|
69
|
+
|
|
66
70
|
@abstractmethod
|
|
67
71
|
def _default_max_duration(self) -> Optional[int]:
|
|
68
72
|
pass
|
|
@@ -104,6 +108,7 @@ class JobConfigurator(ABC):
|
|
|
104
108
|
image_name=self._image_name(),
|
|
105
109
|
user=await self._user(),
|
|
106
110
|
privileged=self._privileged(),
|
|
111
|
+
single_branch=self._single_branch(),
|
|
107
112
|
max_duration=self._max_duration(),
|
|
108
113
|
stop_duration=self._stop_duration(),
|
|
109
114
|
registry_auth=self._registry_auth(),
|
|
@@ -172,6 +177,11 @@ class JobConfigurator(ABC):
|
|
|
172
177
|
def _privileged(self) -> bool:
|
|
173
178
|
return self.run_spec.configuration.privileged
|
|
174
179
|
|
|
180
|
+
def _single_branch(self) -> bool:
|
|
181
|
+
if self.run_spec.configuration.single_branch is None:
|
|
182
|
+
return self._default_single_branch()
|
|
183
|
+
return self.run_spec.configuration.single_branch
|
|
184
|
+
|
|
175
185
|
def _max_duration(self) -> Optional[int]:
|
|
176
186
|
if self.run_spec.merged_profile.max_duration in [None, True]:
|
|
177
187
|
return self._default_max_duration()
|
|
@@ -40,6 +40,9 @@ class DevEnvironmentJobConfigurator(JobConfigurator):
|
|
|
40
40
|
commands += ["tail -f /dev/null"] # idle
|
|
41
41
|
return commands
|
|
42
42
|
|
|
43
|
+
def _default_single_branch(self) -> bool:
|
|
44
|
+
return False
|
|
45
|
+
|
|
43
46
|
def _default_max_duration(self) -> Optional[int]:
|
|
44
47
|
return DEFAULT_MAX_DURATION_SECONDS
|
|
45
48
|
|
|
@@ -11,6 +11,9 @@ class ServiceJobConfigurator(JobConfigurator):
|
|
|
11
11
|
def _shell_commands(self) -> List[str]:
|
|
12
12
|
return self.run_spec.configuration.commands
|
|
13
13
|
|
|
14
|
+
def _default_single_branch(self) -> bool:
|
|
15
|
+
return True
|
|
16
|
+
|
|
14
17
|
def _default_max_duration(self) -> Optional[int]:
|
|
15
18
|
return None
|
|
16
19
|
|
|
@@ -25,6 +25,9 @@ class TaskJobConfigurator(JobConfigurator):
|
|
|
25
25
|
def _shell_commands(self) -> List[str]:
|
|
26
26
|
return self.run_spec.configuration.commands
|
|
27
27
|
|
|
28
|
+
def _default_single_branch(self) -> bool:
|
|
29
|
+
return True
|
|
30
|
+
|
|
28
31
|
def _default_max_duration(self) -> Optional[int]:
|
|
29
32
|
return DEFAULT_MAX_DURATION_SECONDS
|
|
30
33
|
|
|
@@ -12,6 +12,7 @@ from dstack._internal.proxy.lib.services.service_connection import (
|
|
|
12
12
|
ServiceConnectionPool,
|
|
13
13
|
get_service_replica_client,
|
|
14
14
|
)
|
|
15
|
+
from dstack._internal.utils.common import concat_url_path
|
|
15
16
|
from dstack._internal.utils.logging import get_logger
|
|
16
17
|
|
|
17
18
|
logger = get_logger(__name__)
|
|
@@ -37,6 +38,9 @@ async def proxy(
|
|
|
37
38
|
|
|
38
39
|
client = await get_service_replica_client(service, repo, service_conn_pool)
|
|
39
40
|
|
|
41
|
+
if not service.strip_prefix:
|
|
42
|
+
path = concat_url_path(request.scope.get("root_path", "/"), request.url.path)
|
|
43
|
+
|
|
40
44
|
try:
|
|
41
45
|
upstream_request = await build_upstream_request(request, path, client)
|
|
42
46
|
except ClientDisconnect:
|
|
@@ -330,7 +330,7 @@ async def get_plan(
|
|
|
330
330
|
multinode=jobs[0].job_spec.jobs_per_replica > 1,
|
|
331
331
|
volumes=volumes,
|
|
332
332
|
privileged=jobs[0].job_spec.privileged,
|
|
333
|
-
instance_mounts=
|
|
333
|
+
instance_mounts=check_run_spec_requires_instance_mounts(run_spec),
|
|
334
334
|
)
|
|
335
335
|
|
|
336
336
|
job_plans = []
|
|
@@ -897,9 +897,10 @@ def get_offer_mount_point_volume(
|
|
|
897
897
|
raise ServerClientError("Failed to find an eligible volume for the mount point")
|
|
898
898
|
|
|
899
899
|
|
|
900
|
-
def
|
|
900
|
+
def check_run_spec_requires_instance_mounts(run_spec: RunSpec) -> bool:
|
|
901
901
|
return any(
|
|
902
|
-
is_core_model_instance(mp, InstanceMountPoint)
|
|
902
|
+
is_core_model_instance(mp, InstanceMountPoint) and not mp.optional
|
|
903
|
+
for mp in run_spec.configuration.volumes
|
|
903
904
|
)
|
|
904
905
|
|
|
905
906
|
|
|
@@ -967,7 +968,7 @@ def _validate_run_spec_and_set_defaults(run_spec: RunSpec):
|
|
|
967
968
|
_UPDATABLE_SPEC_FIELDS = ["repo_code_hash", "configuration"]
|
|
968
969
|
# Most service fields can be updated via replica redeployment.
|
|
969
970
|
# TODO: Allow updating other fields when a rolling deployment is supported.
|
|
970
|
-
_UPDATABLE_CONFIGURATION_FIELDS = ["replicas", "scaling"]
|
|
971
|
+
_UPDATABLE_CONFIGURATION_FIELDS = ["replicas", "scaling", "strip_prefix"]
|
|
971
972
|
|
|
972
973
|
|
|
973
974
|
def _can_update_run_spec(current_run_spec: RunSpec, new_run_spec: RunSpec) -> bool:
|
dstack/api/_public/runs.py
CHANGED
|
@@ -324,11 +324,19 @@ class Run(ABC):
|
|
|
324
324
|
if runtime_data is not None and runtime_data.ports is not None:
|
|
325
325
|
container_ssh_port = runtime_data.ports.get(container_ssh_port, container_ssh_port)
|
|
326
326
|
|
|
327
|
+
# TODO: get login name from runner in case it's not specified in the run configuration
|
|
328
|
+
# (i.e. the default image user is used, and it is not root)
|
|
329
|
+
if job.job_spec.user is not None and job.job_spec.user.username is not None:
|
|
330
|
+
container_user = job.job_spec.user.username
|
|
331
|
+
else:
|
|
332
|
+
container_user = "root"
|
|
333
|
+
|
|
327
334
|
self._ssh_attach = SSHAttach(
|
|
328
335
|
hostname=provisioning_data.hostname,
|
|
329
336
|
ssh_port=provisioning_data.ssh_port,
|
|
330
337
|
container_ssh_port=container_ssh_port,
|
|
331
338
|
user=provisioning_data.username,
|
|
339
|
+
container_user=container_user,
|
|
332
340
|
id_rsa_path=ssh_identity_file,
|
|
333
341
|
ports_lock=self._ports_lock,
|
|
334
342
|
run_name=name,
|
dstack/api/server/__init__.py
CHANGED
|
@@ -133,9 +133,16 @@ class APIClient:
|
|
|
133
133
|
else:
|
|
134
134
|
raise ClientError(f"Failed to connect to dstack server {self._base_url}")
|
|
135
135
|
|
|
136
|
+
if 400 <= resp.status_code < 600:
|
|
137
|
+
logger.debug(
|
|
138
|
+
"Error requesting %s. Status: %s. Headers: %s. Body: %s",
|
|
139
|
+
resp.request.url,
|
|
140
|
+
resp.status_code,
|
|
141
|
+
resp.headers,
|
|
142
|
+
resp.content,
|
|
143
|
+
)
|
|
144
|
+
|
|
136
145
|
if raise_for_status:
|
|
137
|
-
if resp.status_code == 500:
|
|
138
|
-
raise ClientError("Unexpected dstack server error")
|
|
139
146
|
if resp.status_code == 400: # raise ServerClientError
|
|
140
147
|
detail: List[Dict] = resp.json()["detail"]
|
|
141
148
|
if len(detail) == 1 and detail[0]["code"] in _server_client_errors:
|
|
@@ -145,7 +152,16 @@ class APIClient:
|
|
|
145
152
|
if resp.status_code == 422:
|
|
146
153
|
formatted_error = pprint.pformat(resp.json())
|
|
147
154
|
raise ClientError(f"Server validation error: \n{formatted_error}")
|
|
148
|
-
resp.
|
|
155
|
+
if resp.status_code == 403:
|
|
156
|
+
raise ClientError(
|
|
157
|
+
f"Access to {resp.request.url} is denied. Please check your access token"
|
|
158
|
+
)
|
|
159
|
+
if 400 <= resp.status_code < 600:
|
|
160
|
+
raise ClientError(
|
|
161
|
+
f"Unexpected error: status code {resp.status_code}"
|
|
162
|
+
f" when requesting {resp.request.url}."
|
|
163
|
+
" Check server logs or run with DSTACK_CLI_LOG_LEVEL=DEBUG to see more details"
|
|
164
|
+
)
|
|
149
165
|
return resp
|
|
150
166
|
|
|
151
167
|
|
dstack/api/server/_runs.py
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from typing import List, Optional, Union
|
|
2
|
+
from typing import Any, List, Optional, Union
|
|
3
3
|
from uuid import UUID
|
|
4
4
|
|
|
5
5
|
from pydantic import parse_obj_as
|
|
6
6
|
|
|
7
|
+
from dstack._internal.core.models.common import is_core_model_instance
|
|
8
|
+
from dstack._internal.core.models.configurations import (
|
|
9
|
+
STRIP_PREFIX_DEFAULT,
|
|
10
|
+
ServiceConfiguration,
|
|
11
|
+
)
|
|
7
12
|
from dstack._internal.core.models.pools import Instance
|
|
8
13
|
from dstack._internal.core.models.profiles import Profile
|
|
9
14
|
from dstack._internal.core.models.runs import (
|
|
@@ -14,6 +19,7 @@ from dstack._internal.core.models.runs import (
|
|
|
14
19
|
RunPlan,
|
|
15
20
|
RunSpec,
|
|
16
21
|
)
|
|
22
|
+
from dstack._internal.core.models.volumes import InstanceMountPoint
|
|
17
23
|
from dstack._internal.server.schemas.runs import (
|
|
18
24
|
ApplyRunPlanRequest,
|
|
19
25
|
CreateInstanceRequest,
|
|
@@ -117,34 +123,48 @@ class RunsAPIClient(APIClientGroup):
|
|
|
117
123
|
|
|
118
124
|
def _get_run_spec_excludes(run_spec: RunSpec) -> Optional[dict]:
|
|
119
125
|
spec_excludes: dict[str, set[str]] = {}
|
|
120
|
-
configuration_excludes:
|
|
126
|
+
configuration_excludes: dict[str, Any] = {}
|
|
121
127
|
profile_excludes: set[str] = set()
|
|
122
128
|
configuration = run_spec.configuration
|
|
123
129
|
profile = run_spec.profile
|
|
124
130
|
|
|
125
131
|
# client >= 0.18.18 / server <= 0.18.17 compatibility tweak
|
|
126
132
|
if not configuration.privileged:
|
|
127
|
-
configuration_excludes
|
|
133
|
+
configuration_excludes["privileged"] = True
|
|
128
134
|
# client >= 0.18.23 / server <= 0.18.22 compatibility tweak
|
|
129
135
|
if configuration.type == "service" and configuration.gateway is None:
|
|
130
|
-
configuration_excludes
|
|
136
|
+
configuration_excludes["gateway"] = True
|
|
131
137
|
# client >= 0.18.30 / server <= 0.18.29 compatibility tweak
|
|
132
138
|
if run_spec.configuration.user is None:
|
|
133
|
-
configuration_excludes
|
|
139
|
+
configuration_excludes["user"] = True
|
|
134
140
|
# client >= 0.18.30 / server <= 0.18.29 compatibility tweak
|
|
135
141
|
if configuration.reservation is None:
|
|
136
|
-
configuration_excludes
|
|
142
|
+
configuration_excludes["reservation"] = True
|
|
137
143
|
if profile is not None and profile.reservation is None:
|
|
138
144
|
profile_excludes.add("reservation")
|
|
139
145
|
if configuration.idle_duration is None:
|
|
140
|
-
configuration_excludes
|
|
146
|
+
configuration_excludes["idle_duration"] = True
|
|
141
147
|
if profile is not None and profile.idle_duration is None:
|
|
142
148
|
profile_excludes.add("idle_duration")
|
|
143
149
|
# client >= 0.18.38 / server <= 0.18.37 compatibility tweak
|
|
144
150
|
if configuration.stop_duration is None:
|
|
145
|
-
configuration_excludes
|
|
151
|
+
configuration_excludes["stop_duration"] = True
|
|
146
152
|
if profile is not None and profile.stop_duration is None:
|
|
147
153
|
profile_excludes.add("stop_duration")
|
|
154
|
+
# client >= 0.18.40 / server <= 0.18.39 compatibility tweak
|
|
155
|
+
if (
|
|
156
|
+
is_core_model_instance(configuration, ServiceConfiguration)
|
|
157
|
+
and configuration.strip_prefix == STRIP_PREFIX_DEFAULT
|
|
158
|
+
):
|
|
159
|
+
configuration_excludes["strip_prefix"] = True
|
|
160
|
+
if configuration.single_branch is None:
|
|
161
|
+
configuration_excludes["single_branch"] = True
|
|
162
|
+
if all(
|
|
163
|
+
not is_core_model_instance(v, InstanceMountPoint) or not v.optional
|
|
164
|
+
for v in configuration.volumes
|
|
165
|
+
):
|
|
166
|
+
configuration_excludes["volumes"] = {"__all__": {"optional"}}
|
|
167
|
+
|
|
148
168
|
if configuration_excludes:
|
|
149
169
|
spec_excludes["configuration"] = configuration_excludes
|
|
150
170
|
if profile_excludes:
|
dstack/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dstack
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.40rc1
|
|
4
4
|
Summary: dstack is an open-source orchestration engine for running AI workloads on any cloud or on-premises.
|
|
5
5
|
Home-page: https://dstack.ai
|
|
6
6
|
Author: Andrey Cheptsov
|
|
@@ -303,17 +303,16 @@ for AI workloads both in the cloud and on-prem, speeding up the development, tra
|
|
|
303
303
|
|
|
304
304
|
#### Accelerators
|
|
305
305
|
|
|
306
|
-
`dstack` supports `NVIDIA
|
|
306
|
+
`dstack` supports `NVIDIA`, `AMD`, `Google TPU`, and `Intel Gaudi` accelerators out of the box.
|
|
307
307
|
|
|
308
308
|
## Major news ✨
|
|
309
309
|
|
|
310
|
-
- [2025/01] [dstack 0.18.
|
|
311
|
-
- [
|
|
310
|
+
- [2025/01] [dstack 0.18.38: Intel Gaudi](https://github.com/dstackai/dstack/releases/tag/0.18.38)
|
|
311
|
+
- [2025/01] [dstack 0.18.35: Vultr](https://github.com/dstackai/dstack/releases/tag/0.18.35)
|
|
312
|
+
- [2024/12] [dstack 0.18.32: TPU v6e](https://github.com/dstackai/dstack/releases/tag/0.18.32)
|
|
312
313
|
- [2024/12] [dstack 0.18.30: AWS Capacity Reservations and Capacity Blocks](https://github.com/dstackai/dstack/releases/tag/0.18.30)
|
|
313
|
-
- [2024/11] [dstack 0.18.23: Gateway is optional](https://github.com/dstackai/dstack/releases/tag/0.18.23)
|
|
314
314
|
- [2024/10] [dstack 0.18.21: Instance volumes](https://github.com/dstackai/dstack/releases/tag/0.18.21)
|
|
315
|
-
- [2024/10] [dstack 0.18.18: Hardware metrics](https://github.com/dstackai/dstack/releases/tag/0.18.18)
|
|
316
|
-
- [2024/10] [dstack 0.18.17: AMD support with SSH fleets, AWS EFA](https://github.com/dstackai/dstack/releases/tag/0.18.17)
|
|
315
|
+
- [2024/10] [dstack 0.18.18: Hardware metrics monitoring](https://github.com/dstackai/dstack/releases/tag/0.18.18)
|
|
317
316
|
|
|
318
317
|
## Installation
|
|
319
318
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
dstack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
dstack/version.py,sha256=
|
|
2
|
+
dstack/version.py,sha256=C2zHMrldaVFflhC9dNqTI0mPTOgTGyqywHXOnApNTEE,68
|
|
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=8XODoSW2joaEndvZxuHUPSFK85sGgJ7fVL976isYeJM,557
|
|
@@ -27,12 +27,12 @@ dstack/_internal/cli/services/profile.py,sha256=Fl052TeMCbWO1Q6HiaPVD9CdfClzZAkk
|
|
|
27
27
|
dstack/_internal/cli/services/repos.py,sha256=n2INRJtTYV5y60-Yp3F-V5kJkqwZI_fdGXPnxvZhkNw,3321
|
|
28
28
|
dstack/_internal/cli/services/configurators/__init__.py,sha256=z94VPBFqybP8Zpwy3CzYxmpPAqYBOvRRLpXoz2H4GKI,2697
|
|
29
29
|
dstack/_internal/cli/services/configurators/base.py,sha256=YsyNmHTv1wVbvaAjQi_qNQAb4oK9T_Wgu6RKg-uyzvY,3379
|
|
30
|
-
dstack/_internal/cli/services/configurators/fleet.py,sha256=
|
|
31
|
-
dstack/_internal/cli/services/configurators/gateway.py,sha256=
|
|
30
|
+
dstack/_internal/cli/services/configurators/fleet.py,sha256=iqNJSoji7IiVb3Kb4KA6XTzI5k2xUYF7DK9aE7iverk,14281
|
|
31
|
+
dstack/_internal/cli/services/configurators/gateway.py,sha256=BlLRBC-rl9a0xpO3c1N4k5yinvVQB5YU238g-8NMINY,8389
|
|
32
32
|
dstack/_internal/cli/services/configurators/run.py,sha256=GLGhCcrOzp96Yc6RiISg-DZEM43WT7bhsZmD8z0oAVA,23271
|
|
33
33
|
dstack/_internal/cli/services/configurators/volume.py,sha256=UAKfx-J5fUdnnR2RBvbRJy0YKzXOoub4f4ywMsZwk38,7983
|
|
34
34
|
dstack/_internal/cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
dstack/_internal/cli/utils/common.py,sha256=
|
|
35
|
+
dstack/_internal/cli/utils/common.py,sha256=rfmzqrsgR3rXW3wj0vxDdvrhUUg2aIy4A6E9MZbd55g,1763
|
|
36
36
|
dstack/_internal/cli/utils/fleet.py,sha256=tumFG4qgrKjmb0Iik64tWcPrenaOjpeXC6AZSezQ3_A,2991
|
|
37
37
|
dstack/_internal/cli/utils/gateway.py,sha256=jMytH6u3x8hctMhm9bcmXLJxSgTXmpW8M9abATQrw3c,1474
|
|
38
38
|
dstack/_internal/cli/utils/rich.py,sha256=Gx1MJU929kMKsbdo9qF7XHARNta2426Ssb-xMLVhwbQ,5710
|
|
@@ -114,7 +114,7 @@ dstack/_internal/core/backends/vultr/config.py,sha256=TqjT2yhW9rAiDpJ96gBFTM638F
|
|
|
114
114
|
dstack/_internal/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
115
|
dstack/_internal/core/models/common.py,sha256=XNfEP_lHAs398oDfSVn8kCX79GIWvl0hDs05Rz1CBic,2579
|
|
116
116
|
dstack/_internal/core/models/config.py,sha256=JJ7rT7dztzTWCY5TkoyxXxTvG5D4IFYhGe7EzwkLOWQ,581
|
|
117
|
-
dstack/_internal/core/models/configurations.py,sha256=
|
|
117
|
+
dstack/_internal/core/models/configurations.py,sha256=BrxbkQ5xhCw-pT7DsEdJeLa92uEWQM3LHPmSiYOohHE,13806
|
|
118
118
|
dstack/_internal/core/models/envs.py,sha256=ls2ahIhEb3LYLX_auH3F46uTRp2zdlMWKEpjmU_V3S4,5017
|
|
119
119
|
dstack/_internal/core/models/fleets.py,sha256=tSAFkBugchMhvLRk8c0loRpteTYnbA0sDhUI8q_EcXI,8969
|
|
120
120
|
dstack/_internal/core/models/gateways.py,sha256=n9lPq6X0n2a7fF-CseiBVDDwasJsU3VQumTetMsfZcc,3465
|
|
@@ -126,13 +126,13 @@ dstack/_internal/core/models/pools.py,sha256=mZSZ_9CKTIhnoXfkQLEOjJg_EDZW7kXXigC
|
|
|
126
126
|
dstack/_internal/core/models/profiles.py,sha256=CRvVf2bph56HOz7OmpCwreOR2yCHdfVgovyjKxYBsiM,8011
|
|
127
127
|
dstack/_internal/core/models/projects.py,sha256=hY3rhLWkuDRsavAdNvQaK6IhnTr7yBHTmNwy9k7zjVE,643
|
|
128
128
|
dstack/_internal/core/models/resources.py,sha256=AzBqGEZLHmg-HpjQtcb_W4pJQRxRtDyeuiHO4berdBA,12408
|
|
129
|
-
dstack/_internal/core/models/runs.py,sha256=
|
|
129
|
+
dstack/_internal/core/models/runs.py,sha256=CqyQ_wMne7G9XHLm2BRH84J0tgG3Ny77No1A8hTCmgQ,16971
|
|
130
130
|
dstack/_internal/core/models/secrets.py,sha256=OlnBI8ESBnpjSqB0-Vr3z8JcqB2Ydfiwo8IJUuM5jAc,219
|
|
131
131
|
dstack/_internal/core/models/server.py,sha256=Hkc1v2s3KOiwslsWVmhUOAzcSeREoG-HD1SzSX9WUGg,152
|
|
132
132
|
dstack/_internal/core/models/services.py,sha256=2Hpi7j0Q1shaf_0wd0C0044AJAmuYi-D3qx3PH849oI,3076
|
|
133
133
|
dstack/_internal/core/models/unix.py,sha256=KxnSQELnkAjjuUgYcQKVkf-UAbYREBD8WCWDvHfOkuA,1915
|
|
134
134
|
dstack/_internal/core/models/users.py,sha256=o_rd0GAmd6jufypVUs9P12NRri3rgAPDt-KxnqNNsGw,703
|
|
135
|
-
dstack/_internal/core/models/volumes.py,sha256=
|
|
135
|
+
dstack/_internal/core/models/volumes.py,sha256=MagqSb9jB2WnQlP-xVYkkPYHtKh9r54ajVEUYNF9iKc,5215
|
|
136
136
|
dstack/_internal/core/models/backends/__init__.py,sha256=iPMAv4j7gpHc0cjt3SxzQGb-sywms8LUXt7IjtKNPnM,5589
|
|
137
137
|
dstack/_internal/core/models/backends/aws.py,sha256=H0RB7pTVb7vjW_-MDpBBwPLbAoKDRtYFHe_n4ppXz6w,2463
|
|
138
138
|
dstack/_internal/core/models/backends/azure.py,sha256=u5R8vLVS85X27P17IZ1L-EcXmxg08sC0E1EFxkVG2YA,1996
|
|
@@ -157,12 +157,12 @@ dstack/_internal/core/models/repos/virtual.py,sha256=1p6k2vBfWI6UT91os3lZfV5wl8b
|
|
|
157
157
|
dstack/_internal/core/services/__init__.py,sha256=Utrh2zbYsP6IBGWMGROaHUefgo_TvLloN3logcdhuC4,282
|
|
158
158
|
dstack/_internal/core/services/api_client.py,sha256=HTQ0fcZciUh-nmfV09hUt__Z4N2zq_R12xAiYXD0Jy0,650
|
|
159
159
|
dstack/_internal/core/services/diff.py,sha256=lw7vTOYnGtRWlEulgVlHt1KjLdC8VJpfBcqMRuFZzSQ,532
|
|
160
|
-
dstack/_internal/core/services/logs.py,sha256=
|
|
160
|
+
dstack/_internal/core/services/logs.py,sha256=7_eJdH4MD-3rVb4A6rIJfjj_p4jzUOCmjRVlPD-UDsg,2166
|
|
161
161
|
dstack/_internal/core/services/profiles.py,sha256=SJGT6LG8JmrfsqlyXOkPZd61BDwZ4Q1ngcmDVgq826M,2160
|
|
162
162
|
dstack/_internal/core/services/repos.py,sha256=YgTmLX8U1Y5cz2dG9yThK1-Geq02iik_C6DGZ0y6uEY,5311
|
|
163
163
|
dstack/_internal/core/services/configs/__init__.py,sha256=2jwu1w0vEDNcKGdeI15SS4EtpLSZzRpG8xZslHgTJmY,5484
|
|
164
164
|
dstack/_internal/core/services/ssh/__init__.py,sha256=UhgC3Lv3CPSGqSPEQZIKOfLKUlCFnaB0uqPQhfKCFt0,878
|
|
165
|
-
dstack/_internal/core/services/ssh/attach.py,sha256=
|
|
165
|
+
dstack/_internal/core/services/ssh/attach.py,sha256=A-A8H9lwdIFrxk6FOtHaCZ02fNXo73yz4c-QN1_BQ98,7489
|
|
166
166
|
dstack/_internal/core/services/ssh/client.py,sha256=FUg74jNOagYR3OKuWMBwNgJWIDwHfYrIMSER8xn92D8,4102
|
|
167
167
|
dstack/_internal/core/services/ssh/ports.py,sha256=yWgcACqTNwfLMG24U28LYXlZuEb1V7cSiYBQBqVN5yU,2884
|
|
168
168
|
dstack/_internal/core/services/ssh/tunnel.py,sha256=lHNK1hEcqMP_T_Y6cM_OLHVIRCEA4hud-sM5Jmp4svM,10296
|
|
@@ -201,7 +201,7 @@ dstack/_internal/proxy/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
201
201
|
dstack/_internal/proxy/lib/auth.py,sha256=bI85UFTupPxlcAJZqpOuqS64I4MeIyzp_gHxM3916V4,183
|
|
202
202
|
dstack/_internal/proxy/lib/deps.py,sha256=fFq1mJQK_XcqCNgORTowhc0jCblRiGIK1TeRDC2wkL8,3538
|
|
203
203
|
dstack/_internal/proxy/lib/errors.py,sha256=sUO1dDMkr0oKghB0yUdoHLhB66tX--FkHWrJ5jzPB4w,437
|
|
204
|
-
dstack/_internal/proxy/lib/models.py,sha256=
|
|
204
|
+
dstack/_internal/proxy/lib/models.py,sha256=duixvPYU6lnusp3tYjxU675Zo3idXfOiAPQ1ZRnv1h8,2678
|
|
205
205
|
dstack/_internal/proxy/lib/repo.py,sha256=zkWZ9XZzQHfCa-eifec7H7UYnJZLgeRuiQls7Rc2pkA,781
|
|
206
206
|
dstack/_internal/proxy/lib/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
207
207
|
dstack/_internal/proxy/lib/routers/model_proxy.py,sha256=57GFRpVRXcVY-347HnUSUr4w4RsxsjLuuZiJs8DwDpM,3895
|
|
@@ -217,9 +217,9 @@ dstack/_internal/proxy/lib/services/model_proxy/clients/openai.py,sha256=K8cMgGQ
|
|
|
217
217
|
dstack/_internal/proxy/lib/services/model_proxy/clients/tgi.py,sha256=7TsqrlUVo2PYdEIbmmGNTz0ktf_T8l4Zpw4w4HYAYTs,7845
|
|
218
218
|
dstack/_internal/proxy/lib/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
219
219
|
dstack/_internal/proxy/lib/testing/auth.py,sha256=-firWTnis9Eogoi58BURv1S-te4Hf9x1Q_aYLZMDll4,465
|
|
220
|
-
dstack/_internal/proxy/lib/testing/common.py,sha256=
|
|
220
|
+
dstack/_internal/proxy/lib/testing/common.py,sha256=5e2VYboMqjBnUxkvidaWLoQ-uaBGh_hnURb_VJc38q4,1518
|
|
221
221
|
dstack/_internal/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
222
|
-
dstack/_internal/server/app.py,sha256=
|
|
222
|
+
dstack/_internal/server/app.py,sha256=pPto7JaBEkwvPF2FR8sqDzwIyMovI_85xk6WXfnnCYU,11317
|
|
223
223
|
dstack/_internal/server/db.py,sha256=WjuqmjG3QAZmSMCeUaJ_ynbowlHuNAvYCZO649cTPHc,3210
|
|
224
224
|
dstack/_internal/server/deps.py,sha256=31e8SU_ogPJWHIDLkgl7cuC_5V91xbJoLyAj17VanfM,670
|
|
225
225
|
dstack/_internal/server/main.py,sha256=kztKhCYNoHSDyJJQScWfZXE0naNleJOCQULW6dd8SGw,109
|
|
@@ -234,7 +234,7 @@ dstack/_internal/server/background/tasks/process_metrics.py,sha256=99Uu41B-XS7QZ
|
|
|
234
234
|
dstack/_internal/server/background/tasks/process_placement_groups.py,sha256=DZctiMF2TXKbYeygoz--ZiC9MXVL1sQJgq0oqRIc7hg,3858
|
|
235
235
|
dstack/_internal/server/background/tasks/process_running_jobs.py,sha256=9vRGefwyjBagmOyot5tjBi6tXP9gwycdrLGqs_-Iu14,27634
|
|
236
236
|
dstack/_internal/server/background/tasks/process_runs.py,sha256=LPe0RvDmNGSnIT2RNEYh5TceFx69z_JPYxwpU8mP9kQ,17254
|
|
237
|
-
dstack/_internal/server/background/tasks/process_submitted_jobs.py,sha256=
|
|
237
|
+
dstack/_internal/server/background/tasks/process_submitted_jobs.py,sha256=CkLdkt3D59RK3UkzLqvHkXZty2Zpf-Ksk3mmVQskHgg,24364
|
|
238
238
|
dstack/_internal/server/background/tasks/process_terminating_jobs.py,sha256=Y_ISp_lY0S0H-Qn7s5t-jVBYiBPoS7080LM5HbP1pRY,3745
|
|
239
239
|
dstack/_internal/server/background/tasks/process_volumes.py,sha256=UoMtYjX6MbSoMXOOuBhA0FVIlQMT_wZWsRNeYVV3cSI,4703
|
|
240
240
|
dstack/_internal/server/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -319,7 +319,7 @@ dstack/_internal/server/schemas/logs.py,sha256=JGt39fBEFRjHhlGT1jIC6kwQhujxPO8ue
|
|
|
319
319
|
dstack/_internal/server/schemas/pools.py,sha256=gPUUXLVwwud_nyOc5cXfSqz-6ladlnBb4Uzu4SJgCg4,734
|
|
320
320
|
dstack/_internal/server/schemas/projects.py,sha256=-YIaAYxMgOMKtw-5xPTmmBBwa_2tjJ3jqVPYEnOeTF8,437
|
|
321
321
|
dstack/_internal/server/schemas/repos.py,sha256=dRmT4CEs1lXN_kB8ZKtL1aZGLuBy-zGfPxLXTn4FwBY,2027
|
|
322
|
-
dstack/_internal/server/schemas/runner.py,sha256=
|
|
322
|
+
dstack/_internal/server/schemas/runner.py,sha256=Avqfu4iorAta_0BTWXfk_ydz4V0Eu0_6ayo3DHzH8HQ,4192
|
|
323
323
|
dstack/_internal/server/schemas/runs.py,sha256=Tp-_q_XijeuH-CR14y5xM1-B4TfTDSzWouVCbsg3NsU,1774
|
|
324
324
|
dstack/_internal/server/schemas/secrets.py,sha256=mfqLSM7PqxVQ-GIWB6RfPRUOvSvvaRv-JxXAYxZ6dyY,373
|
|
325
325
|
dstack/_internal/server/schemas/users.py,sha256=FuDqwRVe3mOmv497vOZKjI0a_d4Wt2g4ZiCJcyfHEKA,495
|
|
@@ -329,7 +329,7 @@ dstack/_internal/server/security/permissions.py,sha256=K756I8fnBLBrq4PZwz1ttt74H
|
|
|
329
329
|
dstack/_internal/server/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
330
330
|
dstack/_internal/server/services/config.py,sha256=rXGihEDrfdDJtDsfeYlbHQW8eGej56VDYTV6BJ_aWjg,29241
|
|
331
331
|
dstack/_internal/server/services/docker.py,sha256=3EcYPiVsrNBGDQYOb60QJ241mzTT6lJROYQXIwt-8dk,5351
|
|
332
|
-
dstack/_internal/server/services/fleets.py,sha256=
|
|
332
|
+
dstack/_internal/server/services/fleets.py,sha256=SOU6m-EVbRdpfTkT7I0UUyVXQJTF4_zpcSNWEIb1lQo,27998
|
|
333
333
|
dstack/_internal/server/services/locking.py,sha256=UV5kc-BgROBuNDpBrp8jkcf-fHPOpHLc8JufL-8mbN8,2453
|
|
334
334
|
dstack/_internal/server/services/logging.py,sha256=Nu1628kW2hqB__N0Eyr07wGWjVWxfyJnczonTJ72kSM,417
|
|
335
335
|
dstack/_internal/server/services/logs.py,sha256=53pymPDaM9-xXHFzCyEHdvM49JPTpsLI2aPTSP5zaPo,20090
|
|
@@ -340,7 +340,7 @@ dstack/_internal/server/services/placement.py,sha256=DWZ8-iAE3o0J0xaHikuJYZzpuBi
|
|
|
340
340
|
dstack/_internal/server/services/pools.py,sha256=nkYsYb47lBnPswl3GleNvm30OIhRw4fPH7Z9PJpmhyo,25154
|
|
341
341
|
dstack/_internal/server/services/projects.py,sha256=HWOnFOC6LmvbjDzRKADv7tXKIDWthbO11zrsHU7vkew,14709
|
|
342
342
|
dstack/_internal/server/services/repos.py,sha256=f9ztN7jz_2gvD9hXF5sJwWDVyG2-NHRfjIdSukowPh8,9342
|
|
343
|
-
dstack/_internal/server/services/runs.py,sha256=
|
|
343
|
+
dstack/_internal/server/services/runs.py,sha256=W8y7oOzLG_b2qsQar7UG0482NUhcWIGCnqFgrUmiGr4,40584
|
|
344
344
|
dstack/_internal/server/services/storage.py,sha256=6I0xI_3_RpJNbKZwHjDnjrEwXGdHfiaeb5li15T-M1I,1884
|
|
345
345
|
dstack/_internal/server/services/users.py,sha256=L-exfxHdhj3TKX-gSjezHrYK6tnrt5qsQs-zZng1tUI,7123
|
|
346
346
|
dstack/_internal/server/services/volumes.py,sha256=GMHUlSjeiIc9LSWKes-ZPRKgyvWD6y1VT8w6mH-URtg,12635
|
|
@@ -372,21 +372,21 @@ dstack/_internal/server/services/gateways/connection.py,sha256=ot3lV85XdmCT45vBW
|
|
|
372
372
|
dstack/_internal/server/services/gateways/pool.py,sha256=0LclTl1tyx-doS78LeaAKjr-SMp98zuwh5f9s06JSd0,1914
|
|
373
373
|
dstack/_internal/server/services/jobs/__init__.py,sha256=cgY4Tp66zIxIxsW_oj8_JUpEnUXuTutwhewzx2off2Y,19593
|
|
374
374
|
dstack/_internal/server/services/jobs/configurators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
375
|
-
dstack/_internal/server/services/jobs/configurators/base.py,sha256=
|
|
376
|
-
dstack/_internal/server/services/jobs/configurators/dev.py,sha256=
|
|
377
|
-
dstack/_internal/server/services/jobs/configurators/service.py,sha256=
|
|
378
|
-
dstack/_internal/server/services/jobs/configurators/task.py,sha256=
|
|
375
|
+
dstack/_internal/server/services/jobs/configurators/base.py,sha256=uqT5bQWjf8IcYrD85_-WYEYoFyMYYn-FAc-sqKE-zQA,8683
|
|
376
|
+
dstack/_internal/server/services/jobs/configurators/dev.py,sha256=2EEtfOpNXP5d9b3ZErE3dA3Co8aAAv1fbx9wAjRZpGY,2018
|
|
377
|
+
dstack/_internal/server/services/jobs/configurators/service.py,sha256=FOWrLE-6YFSMuGqjOwYTLMV4FuIM5lCMDFjS0l0CoLI,929
|
|
378
|
+
dstack/_internal/server/services/jobs/configurators/task.py,sha256=ojCgdMyGak8u00tdu-th6U6w0pGRENV1mPtajwzeA_s,1502
|
|
379
379
|
dstack/_internal/server/services/jobs/configurators/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
380
380
|
dstack/_internal/server/services/jobs/configurators/extensions/base.py,sha256=xJbHxaaSJ1zjn8zuuApP1Xt2uBaedPhhc-IY0NtDDJQ,418
|
|
381
381
|
dstack/_internal/server/services/jobs/configurators/extensions/vscode.py,sha256=DAj8OEVLyL1x8Jko2EXKhnAkcSnlO1sJk6o6eiiVkDI,1611
|
|
382
382
|
dstack/_internal/server/services/proxy/__init__.py,sha256=aklmvGaGXISztQft-nH8R98WRU6x_L0Fx3RwVDhwt3g,85
|
|
383
383
|
dstack/_internal/server/services/proxy/auth.py,sha256=AVhDCnk9KvxJ7Jsd8Xmotl_29V4lNuBw8mQNBhx90Ws,485
|
|
384
384
|
dstack/_internal/server/services/proxy/deps.py,sha256=u4dHHKBlTgC8Fnnl3onErgE1HGKFwyZTwvq0uT6IdQ4,846
|
|
385
|
-
dstack/_internal/server/services/proxy/repo.py,sha256=
|
|
385
|
+
dstack/_internal/server/services/proxy/repo.py,sha256=JaEgvTsWREoI4aK16qdiRWWlJEV8JdyRnjRK90uylqM,6388
|
|
386
386
|
dstack/_internal/server/services/proxy/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
387
387
|
dstack/_internal/server/services/proxy/routers/service_proxy.py,sha256=8rsuenv9LUlFJghV8Ly4vewGmc4pDdVTyuU0xiNLuVQ,1581
|
|
388
388
|
dstack/_internal/server/services/proxy/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
389
|
-
dstack/_internal/server/services/proxy/services/service_proxy.py,sha256=
|
|
389
|
+
dstack/_internal/server/services/proxy/services/service_proxy.py,sha256=4JrSxHqhBYqU1oENii89Db-bzkFWExYrOy-0mNEhWBs,4879
|
|
390
390
|
dstack/_internal/server/services/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
391
391
|
dstack/_internal/server/services/runner/client.py,sha256=G8LTHOfZTqGpkSiv1ceJyckN-o4Xk3Zf4pDt_Da3acI,14805
|
|
392
392
|
dstack/_internal/server/services/runner/ssh.py,sha256=RlfnCS3d6rx06qYUXXbCY7aAjRdxe6Q6A5VbaRwzEbk,3933
|
|
@@ -524,9 +524,9 @@ dstack/api/_public/backends.py,sha256=IBeotauJzlte379LzRM6ByZCl_98QemGZsm3p_WX98
|
|
|
524
524
|
dstack/api/_public/pools.py,sha256=wlTai0px6aQNeKyL-6nOil6DCj-GdozqqDGmnTg4ubw,1033
|
|
525
525
|
dstack/api/_public/repos.py,sha256=LYWy1W3Z-2y_D82Jln6YzU2F_asPULGJq1GM9HAAMIc,6173
|
|
526
526
|
dstack/api/_public/resources.py,sha256=lTQM2nb7_QB2RCwmpQlBkYkpiN_aWjgF78wI7Op_RlQ,2947
|
|
527
|
-
dstack/api/_public/runs.py,sha256=
|
|
527
|
+
dstack/api/_public/runs.py,sha256=3527ziltD9UNTDPMkaHHUXXQog2VLFXKQ0zgmRsnCb4,24718
|
|
528
528
|
dstack/api/huggingface/__init__.py,sha256=oIrEij3wttLZ1yrywEGvCMd6zswMQrX5pPjrqdSi0UA,2201
|
|
529
|
-
dstack/api/server/__init__.py,sha256=
|
|
529
|
+
dstack/api/server/__init__.py,sha256=1Sw1bB8NItIFWlL1bdeUN1Tuwu3uaYOJWFrg1c4VULY,5801
|
|
530
530
|
dstack/api/server/_backends.py,sha256=mMePYgFMHAUma3Gycx54VsaxAL_IFhBRcI4H4eWWMg0,1908
|
|
531
531
|
dstack/api/server/_fleets.py,sha256=aB8eJtiQDKjhBxVkkYhQCaiF0Q5hWeJOXuTFs7SwnBg,3870
|
|
532
532
|
dstack/api/server/_gateways.py,sha256=_OcFOOxUdh4YIA15ZNQQT6mfj0cSKP2jJ_bQXxugS_w,2568
|
|
@@ -536,7 +536,7 @@ dstack/api/server/_metrics.py,sha256=OPb8sLhI_U605sHOPrELgy0_6cNFLJVfpvr-qkEukRM
|
|
|
536
536
|
dstack/api/server/_pools.py,sha256=PSs8R4FAKZk-jtD4GmATceMyipnasOy2XEg-8lmiN28,2715
|
|
537
537
|
dstack/api/server/_projects.py,sha256=g6kNSU6jer8u7Kaut1I0Ft4wRMLBBCQShJf3fOB63hQ,1440
|
|
538
538
|
dstack/api/server/_repos.py,sha256=gf9fAQw3rzqfPGq-uT8I2Ju_Zn6G4aWTKis8gb8Polc,1885
|
|
539
|
-
dstack/api/server/_runs.py,sha256=
|
|
539
|
+
dstack/api/server/_runs.py,sha256=C8vE8uLRjiftRo_wKrr2wpcd_416qy2CsgUNXS3u_yY,6946
|
|
540
540
|
dstack/api/server/_secrets.py,sha256=VqLfrIcmBJtPxNDRkXTG44H5SWoY788YJapScUukvdY,1576
|
|
541
541
|
dstack/api/server/_users.py,sha256=XzhgGKc5Tsr0-xkz3T6rGyWZ1tO7aYNhLux2eE7dAoY,1738
|
|
542
542
|
dstack/api/server/_volumes.py,sha256=E5VlZY44DRkaRUT0swrftYDoEgicDoVIBzzpYkiJhkw,1356
|
|
@@ -578,7 +578,7 @@ tests/_internal/core/models/test_volumes.py,sha256=V47bMK5UI1-1wrTl_rq-WClAF_OjC
|
|
|
578
578
|
tests/_internal/core/models/repos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
579
579
|
tests/_internal/core/models/repos/test_remote.py,sha256=6PQcuk9pVfQFG97Vpj_blkK2Veq8tvP6oRg4MUC9mX4,3041
|
|
580
580
|
tests/_internal/core/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
581
|
-
tests/_internal/core/services/test_logs.py,sha256=
|
|
581
|
+
tests/_internal/core/services/test_logs.py,sha256=EI4qmFTPWEP-SXYMcXddSlrAqz03PTdx6VRvYl3Jinw,5732
|
|
582
582
|
tests/_internal/core/services/ssh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
583
583
|
tests/_internal/core/services/ssh/test_client.py,sha256=xR8O8DiCtPVRxBILiQLS921XjdxzE3CMXHAQt635_HU,2537
|
|
584
584
|
tests/_internal/core/services/ssh/test_tunnel.py,sha256=icgac2ni8SMgh49uwtPll_vxJu0lLN_py1oHXAo4yqU,7317
|
|
@@ -610,7 +610,7 @@ tests/_internal/server/background/tasks/test_process_metrics.py,sha256=k6L0biz7E
|
|
|
610
610
|
tests/_internal/server/background/tasks/test_process_placement_groups.py,sha256=shHqZvS8QoNwQe8J29-aHk2X2HqX-gsxcQiZevU8yuY,1528
|
|
611
611
|
tests/_internal/server/background/tasks/test_process_running_jobs.py,sha256=EzdAYVUowb9cGb_bdvKG11jTHMuL9D48brXVD4V6GFY,18812
|
|
612
612
|
tests/_internal/server/background/tasks/test_process_runs.py,sha256=Ks3K1O-BMyYSbMr-1L76IEzZ9aLQaVjxF6go-Nj-HxY,13668
|
|
613
|
-
tests/_internal/server/background/tasks/test_process_submitted_jobs.py,sha256=
|
|
613
|
+
tests/_internal/server/background/tasks/test_process_submitted_jobs.py,sha256=K4vdsvTWQfcRWVVAN_yRA8FBqkntIvwnZAjZCObWPsk,22633
|
|
614
614
|
tests/_internal/server/background/tasks/test_process_submitted_volumes.py,sha256=njfRDdDzVtDL8rech_004Wf5z5PpoQVWFufKh1yUyYY,2171
|
|
615
615
|
tests/_internal/server/background/tasks/test_process_terminating_jobs.py,sha256=hatdc4ANgQvJiVLfR9jE3URQjzqFD9FI6ZG9ES_vJOo,9541
|
|
616
616
|
tests/_internal/server/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -623,7 +623,7 @@ tests/_internal/server/routers/test_metrics.py,sha256=NSdb_x_1Nwsqgrf6KXBf257qd2
|
|
|
623
623
|
tests/_internal/server/routers/test_pools.py,sha256=pqPfIengCzI74D8ojYZbswEtGYhBXFtAlCvzg2RQr6c,25027
|
|
624
624
|
tests/_internal/server/routers/test_projects.py,sha256=Pa_YL9lYhYt06TFmuZFEf9pxc-K3fM9L29ULf4gCELw,23950
|
|
625
625
|
tests/_internal/server/routers/test_repos.py,sha256=4O4mCDlAPh8xJU_XWtwLhFWRkoTxXvZC1N2DLPyz2AI,17225
|
|
626
|
-
tests/_internal/server/routers/test_runs.py,sha256=
|
|
626
|
+
tests/_internal/server/routers/test_runs.py,sha256=tjY_Q3HVpKky45lCq2TeT_hEhsCqXyCHZnofXy-NYPM,70541
|
|
627
627
|
tests/_internal/server/routers/test_server.py,sha256=ROkuRNNJEkMQuK8guZ3Qy3iRRfiWvPIJJJDc09BI0D4,489
|
|
628
628
|
tests/_internal/server/routers/test_users.py,sha256=5QSLvfn9SroGsZoBGmSTEaaqJcdEO8EUUy_YuvLL8ss,12787
|
|
629
629
|
tests/_internal/server/routers/test_volumes.py,sha256=4nv7ba9VkjbYFyYyQmO0coyav_Z9x5t_ayoiyKmvXVw,15947
|
|
@@ -641,9 +641,9 @@ tests/_internal/server/services/encryption/keys/__init__.py,sha256=47DEQpj8HBSa-
|
|
|
641
641
|
tests/_internal/server/services/encryption/keys/test_aes.py,sha256=BtDb5ZeXKKNkAg7KOLXKjpQivMIWpmZe5zaJg-JHFo0,601
|
|
642
642
|
tests/_internal/server/services/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
643
643
|
tests/_internal/server/services/proxy/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
644
|
-
tests/_internal/server/services/proxy/routers/test_service_proxy.py,sha256=
|
|
644
|
+
tests/_internal/server/services/proxy/routers/test_service_proxy.py,sha256=cGyBwfSgT268CRhkgpTEIM3O1U9z8tSNTYjHQrn3e0E,9843
|
|
645
645
|
tests/_internal/server/services/runner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
646
|
-
tests/_internal/server/services/runner/test_client.py,sha256=
|
|
646
|
+
tests/_internal/server/services/runner/test_client.py,sha256=LRJzmyMOGtURc-S4uICfhHjO2K0fxvCa--yXflnoTDM,16212
|
|
647
647
|
tests/_internal/server/services/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
648
648
|
tests/_internal/server/services/services/test_autoscalers.py,sha256=YMi4W5gKFOpEdutc2Fli1L1DIUxCJLHE7ji-CJoiVac,3986
|
|
649
649
|
tests/_internal/server/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -658,9 +658,9 @@ tests/_internal/utils/test_path.py,sha256=rzS-1YCxsFUocBe42dghLOMFNymPruGrA7bqFZ
|
|
|
658
658
|
tests/_internal/utils/test_ssh.py,sha256=V-cBFPhD--9eM9d1uQQgpj2gnYLA3c43f4cX9uJ6E-U,1743
|
|
659
659
|
tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
660
660
|
tests/api/test_utils.py,sha256=SSSqHcNE5cZVqDq4n2sKZthRoXaZ_Bx7z1AAN5xTM9s,391
|
|
661
|
-
dstack-0.18.
|
|
662
|
-
dstack-0.18.
|
|
663
|
-
dstack-0.18.
|
|
664
|
-
dstack-0.18.
|
|
665
|
-
dstack-0.18.
|
|
666
|
-
dstack-0.18.
|
|
661
|
+
dstack-0.18.40rc1.dist-info/LICENSE.md,sha256=qDABaRGjSKVOib1U8viw2P_96sIK7Puo426784oD9f8,15976
|
|
662
|
+
dstack-0.18.40rc1.dist-info/METADATA,sha256=a1rkEX-5wyY5dIeivhnTmdkNxg5sFPChQ3RgA-3AwJY,17693
|
|
663
|
+
dstack-0.18.40rc1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
664
|
+
dstack-0.18.40rc1.dist-info/entry_points.txt,sha256=GnLrMS8hx3rWAySQjA7tPNhtixV6a-brRkmal1PKoHc,58
|
|
665
|
+
dstack-0.18.40rc1.dist-info/top_level.txt,sha256=3BrIO1zrqxT9P20ymhRM6k15meZXzbPL6ykBlDZG2_k,13
|
|
666
|
+
dstack-0.18.40rc1.dist-info/RECORD,,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
1
3
|
from dstack._internal.core.models.runs import AppSpec
|
|
2
4
|
from dstack._internal.core.services.logs import URLReplacer
|
|
3
5
|
|
|
@@ -126,7 +128,18 @@ class TestServiceURLReplacer:
|
|
|
126
128
|
)
|
|
127
129
|
assert replacer(b"http://0.0.0.0:8000/qwerty") == b"https://secure.host.com/qwerty"
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
@pytest.mark.parametrize(
|
|
132
|
+
("in_path", "out_path"),
|
|
133
|
+
[
|
|
134
|
+
("", "/proxy/services/main/service/"),
|
|
135
|
+
("/", "/proxy/services/main/service/"),
|
|
136
|
+
("/a/b/c", "/proxy/services/main/service/a/b/c"),
|
|
137
|
+
("/proxy/services/main/service", "/proxy/services/main/service"),
|
|
138
|
+
("/proxy/services/main/service/", "/proxy/services/main/service/"),
|
|
139
|
+
("/proxy/services/main/service/a/b/c", "/proxy/services/main/service/a/b/c"),
|
|
140
|
+
],
|
|
141
|
+
)
|
|
142
|
+
def test_adds_prefix_unless_already_present(self, in_path: str, out_path: str) -> None:
|
|
130
143
|
replacer = URLReplacer(
|
|
131
144
|
ports={8888: 3000},
|
|
132
145
|
app_specs=[],
|
|
@@ -135,9 +148,6 @@ class TestServiceURLReplacer:
|
|
|
135
148
|
path_prefix="/proxy/services/main/service/",
|
|
136
149
|
)
|
|
137
150
|
assert (
|
|
138
|
-
replacer(
|
|
139
|
-
|
|
140
|
-
assert (
|
|
141
|
-
replacer(b"http://0.0.0.0:8888/qwerty")
|
|
142
|
-
== b"http://0.0.0.0:3000/proxy/services/main/service/qwerty"
|
|
151
|
+
replacer(f"http://0.0.0.0:8888{in_path}".encode())
|
|
152
|
+
== f"http://0.0.0.0:3000{out_path}".encode()
|
|
143
153
|
)
|
|
@@ -302,6 +302,78 @@ class TestProcessSubmittedJobs:
|
|
|
302
302
|
await session.refresh(pool)
|
|
303
303
|
assert not pool.instances
|
|
304
304
|
|
|
305
|
+
@pytest.mark.asyncio
|
|
306
|
+
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
|
|
307
|
+
async def test_provisions_job_with_optional_instance_volume_not_attached(
|
|
308
|
+
self,
|
|
309
|
+
test_db,
|
|
310
|
+
session: AsyncSession,
|
|
311
|
+
):
|
|
312
|
+
project = await create_project(session=session)
|
|
313
|
+
user = await create_user(session=session)
|
|
314
|
+
pool = await create_pool(session=session, project=project)
|
|
315
|
+
repo = await create_repo(
|
|
316
|
+
session=session,
|
|
317
|
+
project_id=project.id,
|
|
318
|
+
)
|
|
319
|
+
run_spec = get_run_spec(run_name="test-run", repo_id=repo.name)
|
|
320
|
+
run_spec.configuration.volumes = [
|
|
321
|
+
InstanceMountPoint(instance_path="/root/.cache", path="/cache", optional=True)
|
|
322
|
+
]
|
|
323
|
+
run = await create_run(
|
|
324
|
+
session=session,
|
|
325
|
+
project=project,
|
|
326
|
+
repo=repo,
|
|
327
|
+
user=user,
|
|
328
|
+
run_name="test-run",
|
|
329
|
+
run_spec=run_spec,
|
|
330
|
+
)
|
|
331
|
+
job = await create_job(
|
|
332
|
+
session=session,
|
|
333
|
+
run=run,
|
|
334
|
+
instance_assigned=True,
|
|
335
|
+
)
|
|
336
|
+
offer = InstanceOfferWithAvailability(
|
|
337
|
+
backend=BackendType.RUNPOD,
|
|
338
|
+
instance=InstanceType(
|
|
339
|
+
name="instance",
|
|
340
|
+
resources=Resources(cpus=1, memory_mib=512, spot=False, gpus=[]),
|
|
341
|
+
),
|
|
342
|
+
region="us",
|
|
343
|
+
price=1.0,
|
|
344
|
+
availability=InstanceAvailability.AVAILABLE,
|
|
345
|
+
)
|
|
346
|
+
with patch("dstack._internal.server.services.backends.get_project_backends") as m:
|
|
347
|
+
backend_mock = Mock()
|
|
348
|
+
m.return_value = [backend_mock]
|
|
349
|
+
backend_mock.TYPE = BackendType.RUNPOD
|
|
350
|
+
backend_mock.compute.return_value.get_offers_cached.return_value = [offer]
|
|
351
|
+
backend_mock.compute.return_value.run_job.return_value = JobProvisioningData(
|
|
352
|
+
backend=offer.backend,
|
|
353
|
+
instance_type=offer.instance,
|
|
354
|
+
instance_id="instance_id",
|
|
355
|
+
hostname="1.1.1.1",
|
|
356
|
+
internal_ip=None,
|
|
357
|
+
region=offer.region,
|
|
358
|
+
price=offer.price,
|
|
359
|
+
username="ubuntu",
|
|
360
|
+
ssh_port=22,
|
|
361
|
+
ssh_proxy=None,
|
|
362
|
+
dockerized=False,
|
|
363
|
+
backend_data=None,
|
|
364
|
+
)
|
|
365
|
+
await process_submitted_jobs()
|
|
366
|
+
|
|
367
|
+
await session.refresh(job)
|
|
368
|
+
assert job is not None
|
|
369
|
+
assert job.status == JobStatus.PROVISIONING
|
|
370
|
+
|
|
371
|
+
await session.refresh(pool)
|
|
372
|
+
instance_offer = InstanceOfferWithAvailability.parse_raw(pool.instances[0].offer)
|
|
373
|
+
assert offer == instance_offer
|
|
374
|
+
pool_job_provisioning_data = pool.instances[0].job_provisioning_data
|
|
375
|
+
assert pool_job_provisioning_data == job.job_provisioning_data
|
|
376
|
+
|
|
305
377
|
@pytest.mark.asyncio
|
|
306
378
|
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
|
|
307
379
|
async def test_fails_job_when_no_capacity(self, test_db, session: AsyncSession):
|
|
@@ -412,7 +484,8 @@ class TestProcessSubmittedJobs:
|
|
|
412
484
|
run_spec = get_run_spec(run_name="test-run", repo_id=repo.name)
|
|
413
485
|
run_spec.configuration.volumes = [
|
|
414
486
|
VolumeMountPoint(name=volume.name, path="/volume"),
|
|
415
|
-
InstanceMountPoint(instance_path="/root/.
|
|
487
|
+
InstanceMountPoint(instance_path="/root/.data", path="/data"),
|
|
488
|
+
InstanceMountPoint(instance_path="/root/.cache", path="/cache", optional=True),
|
|
416
489
|
]
|
|
417
490
|
run = await create_run(
|
|
418
491
|
session=session,
|
|
@@ -110,6 +110,7 @@ def get_dev_env_run_plan_dict(
|
|
|
110
110
|
"instance_types": None,
|
|
111
111
|
"creation_policy": None,
|
|
112
112
|
"instance_name": None,
|
|
113
|
+
"single_branch": None,
|
|
113
114
|
"max_duration": "off",
|
|
114
115
|
"stop_duration": None,
|
|
115
116
|
"max_price": None,
|
|
@@ -180,6 +181,7 @@ def get_dev_env_run_plan_dict(
|
|
|
180
181
|
"replica_num": 0,
|
|
181
182
|
"job_num": 0,
|
|
182
183
|
"jobs_per_replica": 1,
|
|
184
|
+
"single_branch": False,
|
|
183
185
|
"max_duration": None,
|
|
184
186
|
"stop_duration": 300,
|
|
185
187
|
"registry_auth": None,
|
|
@@ -261,6 +263,7 @@ def get_dev_env_run_dict(
|
|
|
261
263
|
"instance_types": None,
|
|
262
264
|
"creation_policy": None,
|
|
263
265
|
"instance_name": None,
|
|
266
|
+
"single_branch": None,
|
|
264
267
|
"max_duration": "off",
|
|
265
268
|
"stop_duration": None,
|
|
266
269
|
"max_price": None,
|
|
@@ -331,6 +334,7 @@ def get_dev_env_run_dict(
|
|
|
331
334
|
"replica_num": 0,
|
|
332
335
|
"job_num": 0,
|
|
333
336
|
"jobs_per_replica": 1,
|
|
337
|
+
"single_branch": False,
|
|
334
338
|
"max_duration": None,
|
|
335
339
|
"stop_duration": 300,
|
|
336
340
|
"registry_auth": None,
|
|
@@ -4,6 +4,7 @@ from unittest.mock import patch
|
|
|
4
4
|
import httpx
|
|
5
5
|
import pytest
|
|
6
6
|
from fastapi import FastAPI
|
|
7
|
+
from fastapi.responses import PlainTextResponse
|
|
7
8
|
|
|
8
9
|
from dstack._internal.proxy.gateway.repo.repo import GatewayProxyRepo
|
|
9
10
|
from dstack._internal.proxy.lib.auth import BaseProxyAuthProvider
|
|
@@ -25,6 +26,8 @@ ProxyTestRepo = GatewayProxyRepo
|
|
|
25
26
|
|
|
26
27
|
@pytest.fixture
|
|
27
28
|
def mock_replica_client_httpbin(httpbin) -> Generator[None, None, None]:
|
|
29
|
+
"""Mocks deployed services. Replaces them with httpbin"""
|
|
30
|
+
|
|
28
31
|
with patch(
|
|
29
32
|
"dstack._internal.proxy.lib.services.service_connection.ServiceConnectionPool.get_or_add"
|
|
30
33
|
) as add_connection_mock:
|
|
@@ -34,6 +37,20 @@ def mock_replica_client_httpbin(httpbin) -> Generator[None, None, None]:
|
|
|
34
37
|
yield
|
|
35
38
|
|
|
36
39
|
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def mock_replica_client_path_reporter() -> Generator[None, None, None]:
|
|
42
|
+
"""Mocks deployed services. Replaces them with an app that returns the requested path"""
|
|
43
|
+
|
|
44
|
+
app = FastAPI()
|
|
45
|
+
app.get("{path:path}")(lambda path: PlainTextResponse(path))
|
|
46
|
+
client = ServiceClient(base_url="http://test/", transport=httpx.ASGITransport(app))
|
|
47
|
+
with patch(
|
|
48
|
+
"dstack._internal.proxy.lib.services.service_connection.ServiceConnectionPool.get_or_add"
|
|
49
|
+
) as add_connection_mock:
|
|
50
|
+
add_connection_mock.return_value.client.return_value = client
|
|
51
|
+
yield
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
def make_app(
|
|
38
55
|
repo: BaseProxyRepo, auth: BaseProxyAuthProvider = ProxyTestAuthProvider()
|
|
39
56
|
) -> FastAPI:
|
|
@@ -200,3 +217,25 @@ async def test_auth(mock_replica_client_httpbin, token: Optional[str], status: i
|
|
|
200
217
|
url = "http://test-host/proxy/services/test-proj/httpbin/"
|
|
201
218
|
resp = await client.get(url, headers=headers)
|
|
202
219
|
assert resp.status_code == status
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@pytest.mark.asyncio
|
|
223
|
+
@pytest.mark.parametrize(
|
|
224
|
+
("strip", "downstream_path", "upstream_path"),
|
|
225
|
+
[
|
|
226
|
+
(True, "/proxy/services/my-proj/my-run/", "/"),
|
|
227
|
+
(True, "/proxy/services/my-proj/my-run/a/b", "/a/b"),
|
|
228
|
+
(False, "/proxy/services/my-proj/my-run/", "/proxy/services/my-proj/my-run/"),
|
|
229
|
+
(False, "/proxy/services/my-proj/my-run/a/b", "/proxy/services/my-proj/my-run/a/b"),
|
|
230
|
+
],
|
|
231
|
+
)
|
|
232
|
+
async def test_strip_prefix(
|
|
233
|
+
mock_replica_client_path_reporter, strip: bool, downstream_path: str, upstream_path: str
|
|
234
|
+
) -> None:
|
|
235
|
+
repo = ProxyTestRepo()
|
|
236
|
+
await repo.set_project(make_project("my-proj"))
|
|
237
|
+
await repo.set_service(make_service("my-proj", "my-run", strip_prefix=strip))
|
|
238
|
+
_, client = make_app_client(repo)
|
|
239
|
+
resp = await client.get(f"http://test-host{downstream_path}")
|
|
240
|
+
assert resp.status_code == 200
|
|
241
|
+
assert resp.text == upstream_path
|
|
@@ -175,7 +175,9 @@ class TestShimClientV1(BaseShimClientTest):
|
|
|
175
175
|
"device_name": "/dev/sdv",
|
|
176
176
|
}
|
|
177
177
|
],
|
|
178
|
-
"instance_mounts": [
|
|
178
|
+
"instance_mounts": [
|
|
179
|
+
{"instance_path": "/mnt/nfs/home", "path": "/home", "optional": False}
|
|
180
|
+
],
|
|
179
181
|
}
|
|
180
182
|
self.assert_request(adapter, 0, "POST", "/api/submit", expected_request)
|
|
181
183
|
|
|
@@ -341,7 +343,9 @@ class TestShimClientV2(BaseShimClientTest):
|
|
|
341
343
|
}
|
|
342
344
|
],
|
|
343
345
|
"volume_mounts": [{"name": "vol", "path": "/vol"}],
|
|
344
|
-
"instance_mounts": [
|
|
346
|
+
"instance_mounts": [
|
|
347
|
+
{"instance_path": "/mnt/nfs/home", "path": "/home", "optional": False}
|
|
348
|
+
],
|
|
345
349
|
"host_ssh_user": "dstack",
|
|
346
350
|
"host_ssh_keys": ["host_key"],
|
|
347
351
|
"container_ssh_keys": ["project_key", "user_key"],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|