Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/dstack/_internal/server/services/runs/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
SERVICE_HTTPS_DEFAULT,
ServiceConfiguration,
)
from dstack._internal.core.models.profiles import ProfileRetry
from dstack._internal.core.models.repos.virtual import DEFAULT_VIRTUAL_REPO_ID, VirtualRunRepoData
from dstack._internal.core.models.routers import RouterType
from dstack._internal.core.models.runs import LEGACY_REPO_DIR, AnyRunConfiguration, RunSpec
Expand Down Expand Up @@ -74,6 +75,7 @@ def validate_run_spec_and_set_defaults(
# the defaults depend on the server version, not the client version.
if run_spec.run_name is not None:
validate_dstack_resource_name(run_spec.run_name)
_validate_retry_duration(run_spec)
for mount_point in run_spec.configuration.volumes:
if not is_valid_docker_volume_target(mount_point.path):
raise ServerClientError(f"Invalid volume mount path: {mount_point.path}")
Expand Down Expand Up @@ -132,6 +134,12 @@ def validate_run_spec_and_set_defaults(
run_spec.configuration.working_dir = LEGACY_REPO_DIR


def _validate_retry_duration(run_spec: RunSpec) -> None:
retry = run_spec.merged_profile.retry
if isinstance(retry, ProfileRetry) and retry.duration is not None and retry.duration < 0:
raise ServerClientError("retry.duration cannot be negative")


def _check_dynamo_in_place_update_compatibility(
current_run_spec: RunSpec, new_run_spec: RunSpec
) -> None:
Expand Down
21 changes: 21 additions & 0 deletions src/tests/_internal/server/services/runs/test_spec.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import re
import uuid
from types import SimpleNamespace

import pytest

from dstack._internal.core.errors import ServerClientError
from dstack._internal.core.models.configurations import ServiceConfiguration
from dstack._internal.core.models.files import FileArchiveMapping
from dstack._internal.core.models.profiles import Profile, ProfileRetry
from dstack._internal.core.models.repos.local import LocalRunRepoData
from dstack._internal.core.models.runs import RunSpec
from dstack._internal.server.services.runs.spec import (
_check_can_update_configuration,
check_can_update_run_spec,
validate_run_spec_and_set_defaults,
)
from dstack._internal.server.testing.common import get_run_spec

Expand Down Expand Up @@ -77,6 +80,24 @@ def _run_spec_with_overrides(configuration: ServiceConfiguration, **overrides) -
return RunSpec.parse_obj({**run_spec.dict(), **run_spec_overrides})


class TestValidateRunSpecRetryDuration:
def test_model_accepts_negative_retry_duration_for_backward_compatibility(self):
retry = ProfileRetry(duration=-1)

assert retry.duration == -1

def test_rejects_negative_retry_duration_for_new_run_specs(self):
run_spec = get_run_spec(
repo_id="test-repo",
profile=Profile(name="default", retry=ProfileRetry(duration=-1)),
)

with pytest.raises(ServerClientError, match="retry.duration cannot be negative"):
validate_run_spec_and_set_defaults(
SimpleNamespace(ssh_public_key="ssh-rsa test"), run_spec
)


class TestCheckCanUpdateConfigurationRouterType:
def test_sglang_to_dynamo_router_type_change_is_rejected(self):
current = _run_spec(_service_configuration(router_type="sglang"))
Expand Down
Loading