-
Notifications
You must be signed in to change notification settings - Fork 164
feat(BA-3917): Define KernelV2 GraphQL schema types with structured fields
#8079
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds new strawberry-based GraphQL types for kernel management (KernelV2) to replace the legacy KernelNode. The changes introduce:
Changes:
- Structured GraphQL types to replace JSON scalar fields (ResourceOpts, ServicePorts, StatusHistory, AttachedDevices, VFolderMount)
- Enum types for SessionTypes, SessionResult, MountPermission, and VFolderUsageMode
- Relay pagination support with filtering and ordering capabilities via KernelFilterGQL and KernelOrderByGQL
- Two new order methods in KernelOrders:
created_at()andid()for ordering support
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/ai/backend/manager/repositories/scheduler/options.py | Adds created_at() and id() order methods to KernelOrders class for query ordering |
| src/ai/backend/manager/api/gql/kernel/types.py | Defines comprehensive KernelV2 GraphQL types with structured fields, enums, filtering, and pagination support |
| src/ai/backend/manager/api/gql/kernel/init.py | Exports the new kernel GraphQL types for use throughout the application |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| case KernelOrderFieldGQL.CREATED_AT: | ||
| return KernelOrders.created_at(ascending) | ||
| case KernelOrderFieldGQL.ID: | ||
| return KernelOrders.id(ascending) |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The to_query_order method doesn't handle all possible enum cases and has no default/fallback, which could cause a runtime error if a new enum value is added. Add a default case or raise an explicit error for unhandled values.
| return KernelOrders.id(ascending) | |
| return KernelOrders.id(ascending) | |
| case _: | |
| raise ValueError(f"Unhandled KernelOrderFieldGQL value: {self.field!r}") |
| entries.append( | ||
| ServicePortEntryGQL( | ||
| name=name, | ||
| protocol=ServicePortProtocolGQL(port_info.get("protocol", "tcp")), |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential ValueError if the protocol string from port_info is not a valid ServicePortProtocolGQL enum value. Wrap this in a try-except or validate the value before creating the enum.
| entries.append( | |
| ServicePortEntryGQL( | |
| name=name, | |
| protocol=ServicePortProtocolGQL(port_info.get("protocol", "tcp")), | |
| protocol_value = port_info.get("protocol", "tcp") | |
| try: | |
| protocol = ServicePortProtocolGQL(protocol_value) | |
| except ValueError: | |
| # Fallback to the default protocol if the provided value is invalid. | |
| try: | |
| protocol = ServicePortProtocolGQL("tcp") | |
| except ValueError: | |
| # If even the default is invalid, skip this entry to avoid runtime errors. | |
| continue | |
| entries.append( | |
| ServicePortEntryGQL( | |
| name=name, | |
| protocol=protocol, |
| return cls( | ||
| name=data.get("name", ""), | ||
| vfid=str(data.get("vfid", "")), | ||
| vfsubpath=str(data.get("vfsubpath", ".")), | ||
| host_path=str(data.get("host_path", "")), | ||
| kernel_path=str(data.get("kernel_path", "")), | ||
| mount_perm=MountPermissionGQL(data.get("mount_perm", "ro")), | ||
| usage_mode=VFolderUsageModeGQL(data.get("usage_mode", "general")), |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential ValueError if mount_perm or usage_mode values from data are not valid enum values. Add error handling or validation before creating these enum instances.
| return cls( | |
| name=data.get("name", ""), | |
| vfid=str(data.get("vfid", "")), | |
| vfsubpath=str(data.get("vfsubpath", ".")), | |
| host_path=str(data.get("host_path", "")), | |
| kernel_path=str(data.get("kernel_path", "")), | |
| mount_perm=MountPermissionGQL(data.get("mount_perm", "ro")), | |
| usage_mode=VFolderUsageModeGQL(data.get("usage_mode", "general")), | |
| raw_mount_perm = data.get("mount_perm", "ro") | |
| if isinstance(raw_mount_perm, MountPermissionGQL): | |
| mount_perm = raw_mount_perm | |
| elif raw_mount_perm in {m.value for m in MountPermissionGQL}: | |
| mount_perm = MountPermissionGQL(raw_mount_perm) | |
| else: | |
| # Fallback to default permission if an invalid value is provided. | |
| mount_perm = MountPermissionGQL("ro") | |
| raw_usage_mode = data.get("usage_mode", "general") | |
| if isinstance(raw_usage_mode, VFolderUsageModeGQL): | |
| usage_mode = raw_usage_mode | |
| elif raw_usage_mode in {m.value for m in VFolderUsageModeGQL}: | |
| usage_mode = VFolderUsageModeGQL(raw_usage_mode) | |
| else: | |
| # Fallback to default usage mode if an invalid value is provided. | |
| usage_mode = VFolderUsageModeGQL("general") | |
| return cls( | |
| name=data.get("name", ""), | |
| vfid=str(data.get("vfid", "")), | |
| vfsubpath=str(data.get("vfsubpath", ".")), | |
| host_path=str(data.get("host_path", "")), | |
| kernel_path=str(data.get("kernel_path", "")), | |
| mount_perm=mount_perm, | |
| usage_mode=usage_mode, |
| entries.append( | ||
| StatusHistoryEntryGQL( | ||
| status=status, | ||
| timestamp=datetime.fromisoformat(timestamp), |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The datetime.fromisoformat() can raise ValueError if the timestamp string is not in the correct ISO format. Add error handling to gracefully handle malformed timestamps.
| entries.append( | |
| StatusHistoryEntryGQL( | |
| status=status, | |
| timestamp=datetime.fromisoformat(timestamp), | |
| try: | |
| parsed_timestamp = datetime.fromisoformat(timestamp) | |
| except ValueError: | |
| # Skip entries with malformed timestamp strings | |
| continue | |
| entries.append( | |
| StatusHistoryEntryGQL( | |
| status=status, | |
| timestamp=parsed_timestamp, |
| count: int | ||
|
|
||
| def __init__(self, *args, count: int, **kwargs) -> None: | ||
| super().__init__(*args, **kwargs) | ||
| self.count = count |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The count field is defined twice - once as a class attribute and once set in __init__. This is redundant. Consider using a strawberry field with a resolver or removing the __init__ method and properly initializing the count field through the Connection initialization.
| count: int | |
| def __init__(self, *args, count: int, **kwargs) -> None: | |
| super().__init__(*args, **kwargs) | |
| self.count = count | |
| count: int = strawberry.field( | |
| description="Total number of kernels matching the filter, ignoring pagination." | |
| ) |
KernelV2 GraphQL types with structured fields
d3547fa to
2ff01ca
Compare
KernelV2 GraphQL types with structured fieldsKernelV2 GraphQL schema types with structured fields
d61149d to
b9d629b
Compare
db70579 to
9ce6282
Compare
9ce6282 to
c39103c
Compare
HyeockJinKim
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please separate PR and write a BEP for this job.
| occupied_slots: ResourceSlotGQL = strawberry.field( | ||
| description=dedent_strip(""" | ||
| The resource slots currently occupied by this kernel. | ||
| Contains entries with resource types (e.g., cpu, mem, cuda.shares) and their quantities. | ||
| """) | ||
| ) | ||
| requested_slots: ResourceSlotGQL = strawberry.field( | ||
| description=dedent_strip(""" | ||
| The resource slots originally requested for this kernel. | ||
| May differ from occupied_slots due to scheduling adjustments. | ||
| """) | ||
| ) | ||
| occupied_shares: ResourceSlotGQL = strawberry.field( | ||
| description="The fractional resource shares occupied by this kernel." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c39103c to
407aa5e
Compare
The contents of the BEP have been reflected in this PR (See BEP-1034 for this) Could you please review it once more? @HyeockJinKim |
cc36dee to
4b229b1
Compare
13ff92f to
837e998
Compare
4b96036 to
1b45b18
Compare
Co-authored-by: octodog <mu001@lablup.com>
Resolves #8082 (BA-3917)
Checklist: (if applicable)
ai.backend.testdocsdirectorySummary
KernelV2GraphQL types replacing legacyKernelNodeResourceOptsGQL,ServicePortsGQL,StatusHistoryGQL,AttachedDevicesGQL,VFolderMountGQLSessionTypesGQL,SessionResultGQL,MountPermissionGQL,VFolderUsageModeGQLKernelOrders.created_at()andKernelOrders.id()for ordering support🤖 Generated with Claude Code
📚 Documentation preview 📚: https://sorna--8079.org.readthedocs.build/en/8079/
📚 Documentation preview 📚: https://sorna-ko--8079.org.readthedocs.build/ko/8079/