From 29fb411928569924a831a358bc669d6864259c44 Mon Sep 17 00:00:00 2001 From: pragnyanramtha Date: Sun, 17 May 2026 03:28:39 +0000 Subject: [PATCH] Reject negative retry durations --- .../_internal/server/services/runs/spec.py | 8 +++++++ .../server/services/runs/test_spec.py | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/dstack/_internal/server/services/runs/spec.py b/src/dstack/_internal/server/services/runs/spec.py index 8be249906..cb989ef5b 100644 --- a/src/dstack/_internal/server/services/runs/spec.py +++ b/src/dstack/_internal/server/services/runs/spec.py @@ -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 @@ -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}") @@ -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: diff --git a/src/tests/_internal/server/services/runs/test_spec.py b/src/tests/_internal/server/services/runs/test_spec.py index a4456b927..093ca768c 100644 --- a/src/tests/_internal/server/services/runs/test_spec.py +++ b/src/tests/_internal/server/services/runs/test_spec.py @@ -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 @@ -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"))