Skip to content

Commit ac0ef89

Browse files
jopemachineclaude
andcommitted
feat(BA-6891): add kernel scheduling-history v2 DTOs and service actions
The admin search is system-wide and unscoped, so its authorization is the SUPERADMIN role gate rather than RBAC scope resolution: it is a BaseGlobalAction run through GlobalActionProcessor, which applies the gate on the action path behind the route's superadmin_required middleware. The scoped counterpart resolves against an RBAC scope and stays on the plain action path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ceb6096 commit ac0ef89

14 files changed

Lines changed: 449 additions & 54 deletions

File tree

changes/12869.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add the v2 DTOs and service actions for kernel scheduling-history search, forming the schema shared by REST v2 and GraphQL.

src/ai/backend/common/dto/manager/v2/scheduling_history/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44

55
from ai.backend.common.dto.manager.v2.scheduling_history.request import (
66
AdminSearchDeploymentHistoriesInput,
7+
AdminSearchKernelHistoriesInput,
78
AdminSearchRouteHistoriesInput,
89
AdminSearchSessionHistoriesInput,
910
DeploymentHistoryFilter,
1011
DeploymentHistoryOrder,
12+
KernelHistoryFilter,
13+
KernelHistoryOrder,
1114
RouteHistoryFilter,
1215
RouteHistoryOrder,
16+
ScopedSearchKernelHistoriesInput,
1317
SearchDeploymentHistoryInput,
1418
SearchRouteHistoryInput,
1519
SearchSessionHistoryInput,
@@ -18,9 +22,11 @@
1822
)
1923
from ai.backend.common.dto.manager.v2.scheduling_history.response import (
2024
AdminSearchDeploymentHistoriesPayload,
25+
AdminSearchKernelHistoriesPayload,
2126
AdminSearchRouteHistoriesPayload,
2227
AdminSearchSessionHistoriesPayload,
2328
DeploymentHistoryNode,
29+
KernelHistoryNode,
2430
ListDeploymentHistoryPayload,
2531
ListRouteHistoryPayload,
2632
ListSessionHistoryPayload,
@@ -30,6 +36,8 @@
3036
from ai.backend.common.dto.manager.v2.scheduling_history.types import (
3137
DeploymentHistoryOrderField,
3238
DeploymentHistoryScopeDTO,
39+
KernelHistoryOrderField,
40+
KernelHistoryScopeDTO,
3341
OrderDirection,
3442
RouteHistoryOrderField,
3543
RouteHistoryScopeDTO,
@@ -43,6 +51,8 @@
4351
# Types
4452
"DeploymentHistoryOrderField",
4553
"DeploymentHistoryScopeDTO",
54+
"KernelHistoryOrderField",
55+
"KernelHistoryScopeDTO",
4656
"OrderDirection",
4757
"RouteHistoryOrderField",
4858
"RouteHistoryScopeDTO",
@@ -52,22 +62,28 @@
5262
"SubStepResultInfo",
5363
# Input models (request)
5464
"AdminSearchDeploymentHistoriesInput",
65+
"AdminSearchKernelHistoriesInput",
5566
"AdminSearchRouteHistoriesInput",
5667
"AdminSearchSessionHistoriesInput",
5768
"DeploymentHistoryFilter",
5869
"DeploymentHistoryOrder",
70+
"KernelHistoryFilter",
71+
"KernelHistoryOrder",
5972
"RouteHistoryFilter",
6073
"RouteHistoryOrder",
74+
"ScopedSearchKernelHistoriesInput",
6175
"SearchDeploymentHistoryInput",
6276
"SearchRouteHistoryInput",
6377
"SearchSessionHistoryInput",
6478
"SessionHistoryFilter",
6579
"SessionHistoryOrder",
6680
# Response models
6781
"AdminSearchDeploymentHistoriesPayload",
82+
"AdminSearchKernelHistoriesPayload",
6883
"AdminSearchRouteHistoriesPayload",
6984
"AdminSearchSessionHistoriesPayload",
7085
"DeploymentHistoryNode",
86+
"KernelHistoryNode",
7187
"ListDeploymentHistoryPayload",
7288
"ListRouteHistoryPayload",
7389
"ListSessionHistoryPayload",

src/ai/backend/common/dto/manager/v2/scheduling_history/request.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
from .types import (
1313
DeploymentHistoryOrderField,
14+
KernelHistoryOrderField,
15+
KernelHistoryScopeDTO,
1416
OrderDirection,
1517
RouteHistoryOrderField,
1618
SchedulingResultType,
@@ -19,13 +21,17 @@
1921

2022
__all__ = (
2123
"AdminSearchDeploymentHistoriesInput",
24+
"AdminSearchKernelHistoriesInput",
2225
"AdminSearchRouteHistoriesInput",
2326
"AdminSearchSessionHistoriesInput",
2427
"DeploymentHistoryFilter",
2528
"DeploymentHistoryOrder",
29+
"KernelHistoryFilter",
30+
"KernelHistoryOrder",
2631
"RouteHistoryFilter",
2732
"RouteHistoryOrder",
2833
"SchedulingResultFilter",
34+
"ScopedSearchKernelHistoriesInput",
2935
"SearchDeploymentHistoryInput",
3036
"SearchRouteHistoryInput",
3137
"SearchSessionHistoryInput",
@@ -86,6 +92,37 @@ class SearchSessionHistoryInput(BaseRequestModel):
8692
offset: int = Field(default=0, ge=0, description="Number of items to skip")
8793

8894

95+
class KernelHistoryFilter(BaseRequestModel):
96+
"""Filter conditions for kernel scheduling history search."""
97+
98+
id: UUIDFilter | None = Field(default=None, description="Filter by history record ID")
99+
kernel_id: UUIDFilter | None = Field(default=None, description="Filter by kernel ID")
100+
session_id: UUIDFilter | None = Field(default=None, description="Filter by session ID")
101+
phase: StringFilter | None = Field(default=None, description="Filter by scheduling phase")
102+
from_status: list[str] | None = Field(default=None, description="Filter by from_status values")
103+
to_status: list[str] | None = Field(default=None, description="Filter by to_status values")
104+
result: SchedulingResultFilter | None = Field(
105+
default=None, description="Filter by scheduling result"
106+
)
107+
error_code: StringFilter | None = Field(default=None, description="Filter by error code")
108+
message: StringFilter | None = Field(default=None, description="Filter by message")
109+
created_at: DateTimeFilter | None = Field(default=None, description="Filter by created_at")
110+
updated_at: DateTimeFilter | None = Field(default=None, description="Filter by updated_at")
111+
AND: list[KernelHistoryFilter] | None = Field(default=None, description="AND conjunction.")
112+
OR: list[KernelHistoryFilter] | None = Field(default=None, description="OR conjunction.")
113+
NOT: list[KernelHistoryFilter] | None = Field(default=None, description="NOT negation.")
114+
115+
116+
KernelHistoryFilter.model_rebuild()
117+
118+
119+
class KernelHistoryOrder(BaseRequestModel):
120+
"""Order specification for kernel scheduling history."""
121+
122+
field: KernelHistoryOrderField = Field(description="Field to order by")
123+
direction: OrderDirection = Field(default=OrderDirection.DESC, description="Order direction")
124+
125+
89126
class DeploymentHistoryFilter(BaseRequestModel):
90127
"""Filter conditions for deployment scheduling history search."""
91128

@@ -182,6 +219,37 @@ class AdminSearchSessionHistoriesInput(BaseRequestModel):
182219
offset: int | None = Field(default=None, description="Offset pagination: number to skip")
183220

184221

222+
class AdminSearchKernelHistoriesInput(BaseRequestModel):
223+
"""Input for admin search of kernel scheduling histories."""
224+
225+
filter: KernelHistoryFilter | None = Field(default=None, description="Filter conditions")
226+
order: list[KernelHistoryOrder] | None = Field(default=None, description="Order specifications")
227+
first: int | None = Field(default=None, description="Cursor pagination: number of items")
228+
after: str | None = Field(default=None, description="Cursor pagination: after cursor")
229+
last: int | None = Field(default=None, description="Cursor pagination: last N items")
230+
before: str | None = Field(default=None, description="Cursor pagination: before cursor")
231+
limit: int | None = Field(default=None, description="Offset pagination: maximum items")
232+
offset: int | None = Field(default=None, description="Offset pagination: number to skip")
233+
234+
235+
class ScopedSearchKernelHistoriesInput(BaseRequestModel):
236+
"""Input for scoped search of kernel scheduling histories.
237+
238+
Unlike the session/deployment/route scoped searches, the scope is carried in the body
239+
because a kernel query may be scoped by session_id, kernel_id, or both.
240+
"""
241+
242+
scope: KernelHistoryScopeDTO = Field(description="Required. Restricts the rows returned.")
243+
filter: KernelHistoryFilter | None = Field(default=None, description="Filter conditions")
244+
order: list[KernelHistoryOrder] | None = Field(default=None, description="Order specifications")
245+
first: int | None = Field(default=None, description="Cursor pagination: number of items")
246+
after: str | None = Field(default=None, description="Cursor pagination: after cursor")
247+
last: int | None = Field(default=None, description="Cursor pagination: last N items")
248+
before: str | None = Field(default=None, description="Cursor pagination: before cursor")
249+
limit: int | None = Field(default=None, description="Offset pagination: maximum items")
250+
offset: int | None = Field(default=None, description="Offset pagination: number to skip")
251+
252+
185253
class AdminSearchDeploymentHistoriesInput(BaseRequestModel):
186254
"""Input for admin search of deployment histories."""
187255

src/ai/backend/common/dto/manager/v2/scheduling_history/response.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
__all__ = (
1818
"AdminSearchDeploymentHistoriesPayload",
19+
"AdminSearchKernelHistoriesPayload",
1920
"AdminSearchRouteHistoriesPayload",
2021
"AdminSearchSessionHistoriesPayload",
2122
"DeploymentHistoryNode",
23+
"KernelHistoryNode",
2224
"ListDeploymentHistoryPayload",
2325
"ListRouteHistoryPayload",
2426
"ListSessionHistoryPayload",
@@ -46,6 +48,26 @@ class SessionHistoryNode(BaseResponseModel):
4648
updated_at: datetime = Field(description="Timestamp when the history record was last updated")
4749

4850

51+
class KernelHistoryNode(BaseResponseModel):
52+
"""Node model representing a kernel scheduling history record.
53+
54+
Has no ``sub_steps``: the ``kernel_scheduling_history`` table carries no such column.
55+
"""
56+
57+
id: UUID = Field(description="History record ID")
58+
kernel_id: UUID = Field(description="Kernel ID this history belongs to")
59+
session_id: UUID = Field(description="Session owning the kernel")
60+
phase: str = Field(description="Scheduling phase")
61+
from_status: str | None = Field(default=None, description="Status before transition")
62+
to_status: str | None = Field(default=None, description="Status after transition")
63+
result: str = Field(description="Result of the scheduling attempt")
64+
error_code: str | None = Field(default=None, description="Error code if scheduling failed")
65+
message: str | None = Field(default=None, description="Human-readable message or error detail")
66+
attempts: int = Field(description="Number of scheduling attempts made")
67+
created_at: datetime = Field(description="Timestamp when the history record was created")
68+
updated_at: datetime = Field(description="Timestamp when the history record was last updated")
69+
70+
4971
class DeploymentHistoryNode(BaseResponseModel):
5072
"""Node model representing a deployment scheduling history record.
5173
@@ -127,6 +149,15 @@ class AdminSearchSessionHistoriesPayload(BaseResponseModel):
127149
has_previous_page: bool = Field(description="Whether there is a previous page.")
128150

129151

152+
class AdminSearchKernelHistoriesPayload(BaseResponseModel):
153+
"""Payload for admin and scoped search of kernel scheduling histories."""
154+
155+
items: list[KernelHistoryNode] = Field(description="List of kernel history nodes.")
156+
total_count: int = Field(description="Total number of records matching the filter.")
157+
has_next_page: bool = Field(description="Whether there is a next page.")
158+
has_previous_page: bool = Field(description="Whether there is a previous page.")
159+
160+
130161
class AdminSearchDeploymentHistoriesPayload(BaseResponseModel):
131162
"""Payload for admin search of deployment histories."""
132163

src/ai/backend/common/dto/manager/v2/scheduling_history/types.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@
88
from enum import StrEnum
99
from uuid import UUID
1010

11-
from pydantic import Field
11+
from pydantic import Field, model_validator
1212

1313
from ai.backend.common.api_handlers import BaseRequestModel, BaseResponseModel
1414
from ai.backend.common.dto.manager.v2.common import OrderDirection
15+
from ai.backend.common.types import KernelId, SessionId
1516

1617
__all__ = (
1718
"DeploymentHistoryOrderField",
1819
"DeploymentHistoryScopeDTO",
1920
"KernelHistoryOrderField",
21+
"KernelHistoryScopeDTO",
2022
"OrderDirection",
2123
"RouteHistoryOrderField",
2224
"RouteHistoryScopeDTO",
@@ -84,6 +86,16 @@ class SessionHistoryScopeDTO(BaseRequestModel):
8486
session_id: UUID = Field(description="Session ID to get history for.")
8587

8688

89+
class KernelHistoryScopeDTO(BaseRequestModel):
90+
"""Scope for kernel scheduling history queries.
91+
92+
The kernel is the access boundary; narrowing by session or any other column is a
93+
filter concern and belongs to ``KernelHistoryFilter``.
94+
"""
95+
96+
kernel_id: KernelId = Field(description="Kernel ID to get history for.")
97+
98+
8799
class DeploymentHistoryScopeDTO(BaseRequestModel):
88100
"""Scope for deployment scheduling history queries."""
89101

src/ai/backend/manager/repositories/scheduling_history/db_source/db_source.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
from collections.abc import Sequence
56
from typing import TYPE_CHECKING
67

78
import sqlalchemy as sa
@@ -22,13 +23,13 @@
2223
RouteHistoryRow,
2324
SessionSchedulingHistoryRow,
2425
)
26+
from ai.backend.manager.models.scopes import SearchScope
2527
from ai.backend.manager.repositories.base import (
2628
BatchQuerier,
2729
execute_batch_querier,
2830
)
2931
from ai.backend.manager.repositories.scheduling_history.types import (
3032
DeploymentHistorySearchScope,
31-
KernelSchedulingHistorySearchScope,
3233
RouteHistorySearchScope,
3334
SessionSchedulingHistorySearchScope,
3435
)
@@ -125,13 +126,13 @@ async def search_kernel_history(
125126
async def search_kernel_scoped_history(
126127
self,
127128
querier: BatchQuerier,
128-
scope: KernelSchedulingHistorySearchScope,
129+
scopes: Sequence[SearchScope],
129130
) -> KernelSchedulingHistoryListResult:
130-
"""Search kernel scheduling history within scope."""
131+
"""Search kernel history whose rows match any of ``scopes`` (OR), narrowed by ``querier``."""
131132
async with self._db.begin_readonly_session() as db_sess:
132133
query = sa.select(KernelSchedulingHistoryRow)
133134

134-
result = await execute_batch_querier(db_sess, query, querier, scopes=[scope])
135+
result = await execute_batch_querier(db_sess, query, querier, scopes=scopes)
135136

136137
items = [row.KernelSchedulingHistoryRow.to_data() for row in result.rows]
137138

src/ai/backend/manager/repositories/scheduling_history/repository.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from collections.abc import Sequence
34
from typing import TYPE_CHECKING
45

56
from ai.backend.common.metrics.metric import DomainType, LayerType
@@ -21,12 +22,12 @@
2122
from ai.backend.manager.data.session.types import (
2223
SessionSchedulingHistoryListResult,
2324
)
25+
from ai.backend.manager.models.scopes import SearchScope
2426
from ai.backend.manager.repositories.base import BatchQuerier
2527

2628
from .db_source import SchedulingHistoryDBSource
2729
from .types import (
2830
DeploymentHistorySearchScope,
29-
KernelSchedulingHistorySearchScope,
3031
RouteHistorySearchScope,
3132
SessionSchedulingHistorySearchScope,
3233
)
@@ -98,10 +99,10 @@ async def search_kernel_history(
9899
async def search_kernel_scoped_history(
99100
self,
100101
querier: BatchQuerier,
101-
scope: KernelSchedulingHistorySearchScope,
102+
scopes: Sequence[SearchScope],
102103
) -> KernelSchedulingHistoryListResult:
103-
"""Search kernel scheduling history within scope."""
104-
return await self._db_source.search_kernel_scoped_history(querier, scope)
104+
"""Search kernel history whose rows match any of ``scopes`` (OR)."""
105+
return await self._db_source.search_kernel_scoped_history(querier, scopes)
105106

106107
# ========== Deployment History (Admin) ==========
107108

0 commit comments

Comments
 (0)