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
7 changes: 6 additions & 1 deletion src/dstack/_internal/server/routers/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from dstack._internal.server.db import get_session
from dstack._internal.server.models import ProjectModel, UserModel
from dstack._internal.server.schemas.runs import (
MAX_JOB_SUBMISSIONS_LIMIT,
ApplyRunPlanRequest,
DeleteRunsRequest,
GetRunPlanRequest,
Expand Down Expand Up @@ -61,10 +62,14 @@ async def list_runs(
`project_name`, `repo_id`, `username`, and `only_active` can be specified as filters.
Setting `only_active` to `true` excludes finished runs and deleted runs.
Specifying `repo_id` without `project_name` returns no runs.
At most `job_submissions_limit` latest job submissions are returned per job.

The results are paginated. To get the next page, pass `submitted_at` and `id` of
the last run from the previous page as `prev_submitted_at` and `prev_run_id`.
"""
job_submissions_limit = body.job_submissions_limit
if job_submissions_limit is None:
job_submissions_limit = MAX_JOB_SUBMISSIONS_LIMIT
run_list = await runs.list_user_runs(
session=session,
user=user,
Expand All @@ -73,7 +78,7 @@ async def list_runs(
username=body.username,
only_active=body.only_active,
include_jobs=body.include_jobs,
job_submissions_limit=body.job_submissions_limit,
job_submissions_limit=job_submissions_limit,
prev_submitted_at=body.prev_submitted_at,
prev_run_id=body.prev_run_id,
limit=body.limit,
Expand Down
6 changes: 5 additions & 1 deletion src/dstack/_internal/server/schemas/runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from dstack._internal.core.models.common import CoreModel
from dstack._internal.core.models.runs import ApplyRunPlanInput, RunSpec

MAX_JOB_SUBMISSIONS_LIMIT = 10


class ListRunsRequest(CoreModel):
project_name: Optional[str] = None
Expand All @@ -20,9 +22,11 @@ class ListRunsRequest(CoreModel):
job_submissions_limit: Optional[int] = Field(
None,
ge=0,
le=MAX_JOB_SUBMISSIONS_LIMIT,
description=(
"Limit number of job submissions returned per job to avoid large responses."
"Drops older job submissions. No effect with `include_jobs: false`"
" Drops older job submissions. No effect with `include_jobs: false`."
f" Defaults to the maximum allowed value of {MAX_JOB_SUBMISSIONS_LIMIT}"
),
)
prev_submitted_at: Optional[datetime] = None
Expand Down
43 changes: 42 additions & 1 deletion src/tests/_internal/server/routers/test_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from dstack._internal.core.models.users import GlobalRole, ProjectRole
from dstack._internal.core.models.volumes import InstanceMountPoint, MountPoint
from dstack._internal.server.models import JobModel, RunModel
from dstack._internal.server.schemas.runs import ApplyRunPlanRequest
from dstack._internal.server.schemas.runs import MAX_JOB_SUBMISSIONS_LIMIT, ApplyRunPlanRequest
from dstack._internal.server.services.projects import add_project_member
from dstack._internal.server.services.resources import (
set_gpu_vendor_default,
Expand Down Expand Up @@ -983,6 +983,47 @@ async def test_limits_job_submissions(
},
]

@pytest.mark.asyncio
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
async def test_limits_job_submissions_by_default(
self, test_db, session: AsyncSession, client: AsyncClient
):
user = await create_user(session=session, global_role=GlobalRole.USER)
project = await create_project(session=session, owner=user)
await add_project_member(
session=session, project=project, user=user, project_role=ProjectRole.USER
)
repo = await create_repo(
session=session,
project_id=project.id,
)
run = await create_run(
session=session,
project=project,
repo=repo,
user=user,
)
submissions_num = MAX_JOB_SUBMISSIONS_LIMIT + 2
for submission_num in range(submissions_num):
await create_job(
session=session,
run=run,
submission_num=submission_num,
)
response = await client.post(
"/api/runs/list",
headers=get_auth_headers(user.token),
json={},
)
assert response.status_code == 200, response.json()
runs = response.json()
assert len(runs) == 1
job_submissions = runs[0]["jobs"][0]["job_submissions"]
assert len(job_submissions) == MAX_JOB_SUBMISSIONS_LIMIT
assert [js["submission_num"] for js in job_submissions] == list(
range(submissions_num - MAX_JOB_SUBMISSIONS_LIMIT, submissions_num)
)

@pytest.mark.asyncio
@pytest.mark.parametrize(
"client_version,expected_probes",
Expand Down
Loading