Skip to content

Commit db1b59b

Browse files
jopemachineclaude
andcommitted
feat(BA-6891): add kernel scheduling-history v2 DTOs and service actions
Add the v2 DTOs and the service layer for kernel scheduling history. The DTOs are the schema shared by REST v2 and GraphQL, so BA-6883 builds its GraphQL types on these rather than defining its own. KernelHistoryScopeDTO carries both scope axes as optional and rejects an empty scope in a model_validator, so the rule lives with the schema both surfaces share instead of being restated per surface. KernelHistoryNode has no sub_steps: kernel_scheduling_history defines no such column, unlike the session/deployment/route tables. BEP-1061 and BA-6852 both describe kernel as carrying sub_steps, which does not match the schema; that belongs to the writer side and is left alone here. Only the two inputs that get wired are added. There is no offset-only SearchKernelHistoryInput mirroring SearchSessionHistoryInput, as that one has no callers. The adapter that converts these DTOs and drives the processors lands in BA-6892. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 497c88e commit db1b59b

10 files changed

Lines changed: 370 additions & 1 deletion

File tree

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: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
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
@@ -17,6 +17,7 @@
1717
"DeploymentHistoryOrderField",
1818
"DeploymentHistoryScopeDTO",
1919
"KernelHistoryOrderField",
20+
"KernelHistoryScopeDTO",
2021
"OrderDirection",
2122
"RouteHistoryOrderField",
2223
"RouteHistoryScopeDTO",
@@ -84,6 +85,25 @@ class SessionHistoryScopeDTO(BaseRequestModel):
8485
session_id: UUID = Field(description="Session ID to get history for.")
8586

8687

88+
class KernelHistoryScopeDTO(BaseRequestModel):
89+
"""Scope for kernel scheduling history queries.
90+
91+
Unlike the single-entity scopes above, a kernel query may be scoped either by the
92+
owning session (all of its kernels) or by one kernel. At least one must be given.
93+
"""
94+
95+
session_id: UUID | None = Field(
96+
default=None, description="Restrict to the kernels of this session."
97+
)
98+
kernel_id: UUID | None = Field(default=None, description="Restrict to this kernel.")
99+
100+
@model_validator(mode="after")
101+
def _check_non_empty(self) -> KernelHistoryScopeDTO:
102+
if self.session_id is None and self.kernel_id is None:
103+
raise ValueError("At least one of session_id or kernel_id must be given.")
104+
return self
105+
106+
87107
class DeploymentHistoryScopeDTO(BaseRequestModel):
88108
"""Scope for deployment scheduling history queries."""
89109

src/ai/backend/manager/services/scheduling_history/actions/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
SearchDeploymentScopedHistoryAction,
1010
SearchDeploymentScopedHistoryActionResult,
1111
)
12+
from .search_kernel_history import (
13+
SearchKernelHistoryAction,
14+
SearchKernelHistoryActionResult,
15+
)
16+
from .search_kernel_scoped_history import (
17+
SearchKernelScopedHistoryAction,
18+
SearchKernelScopedHistoryActionResult,
19+
)
1220
from .search_route_history import (
1321
SearchRouteHistoryAction,
1422
SearchRouteHistoryActionResult,
@@ -31,13 +39,17 @@
3139
# Admin actions
3240
"SearchSessionHistoryAction",
3341
"SearchSessionHistoryActionResult",
42+
"SearchKernelHistoryAction",
43+
"SearchKernelHistoryActionResult",
3444
"SearchDeploymentHistoryAction",
3545
"SearchDeploymentHistoryActionResult",
3646
"SearchRouteHistoryAction",
3747
"SearchRouteHistoryActionResult",
3848
# Scoped actions (added in 26.2.0)
3949
"SearchSessionScopedHistoryAction",
4050
"SearchSessionScopedHistoryActionResult",
51+
"SearchKernelScopedHistoryAction",
52+
"SearchKernelScopedHistoryActionResult",
4153
"SearchDeploymentScopedHistoryAction",
4254
"SearchDeploymentScopedHistoryActionResult",
4355
"SearchRouteScopedHistoryAction",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import override
5+
6+
from ai.backend.common.data.permission.types import EntityType
7+
from ai.backend.manager.actions.action import BaseActionResult
8+
from ai.backend.manager.actions.types import ActionOperationType
9+
from ai.backend.manager.data.kernel.types import KernelSchedulingHistoryData
10+
from ai.backend.manager.repositories.base import BatchQuerier
11+
12+
from .base import SchedulingHistoryAction
13+
14+
15+
@dataclass
16+
class SearchKernelHistoryAction(SchedulingHistoryAction):
17+
"""Action to search kernel scheduling history (admin API)."""
18+
19+
querier: BatchQuerier
20+
21+
@override
22+
@classmethod
23+
def entity_type(cls) -> EntityType:
24+
return EntityType.KERNEL_HISTORY
25+
26+
@override
27+
@classmethod
28+
def operation_type(cls) -> ActionOperationType:
29+
return ActionOperationType.SEARCH
30+
31+
@override
32+
def entity_id(self) -> str | None:
33+
return None
34+
35+
36+
@dataclass
37+
class SearchKernelHistoryActionResult(BaseActionResult):
38+
"""Result of searching kernel scheduling history."""
39+
40+
histories: list[KernelSchedulingHistoryData]
41+
total_count: int
42+
has_next_page: bool
43+
has_previous_page: bool
44+
45+
@override
46+
def entity_id(self) -> str | None:
47+
return None

0 commit comments

Comments
 (0)