-
Notifications
You must be signed in to change notification settings - Fork 165
refactor(BA-3516): Integrate ModifyScalingGroup action with existing GQL resolver
#7524
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
ModifyScalingGroup action with existing GQL resolverModifyScalingGroup action with existing GQL resolver
06dee4b to
92db966
Compare
e4e95b0 to
9741bf3
Compare
d97146e to
bb08812
Compare
9741bf3 to
ac23b1e
Compare
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 PR refactors the ModifyScalingGroup GraphQL mutation to use the new action/repository pattern instead of direct database operations. The refactor integrates the existing ModifyScalingGroupAction with the GraphQL resolver and adds resilience handling to the repository layer.
Key Changes:
- Added resilience decorator to
update_scaling_groupmethod in the repository - Converted
ModifyScalingGroupInputto use anUpdaterpattern via newto_updater()method - Replaced direct SQL update with action-based approach using
ModifyScalingGroupAction
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/ai/backend/manager/repositories/scaling_group/repository.py |
Added @scaling_group_repository_resilience.apply() decorator to update_scaling_group method for consistent error handling |
src/ai/backend/manager/api/gql_legacy/scaling_group.py |
Refactored ModifyScalingGroup mutation to use action pattern; added to_updater() method to convert GraphQL input to repository updater spec |
changes/7524.enhance.md |
Added changelog entry documenting the integration |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| is_active=( | ||
| OptionalState.update(self.is_active) | ||
| if self.is_active is not None | ||
| else OptionalState.nop() | ||
| ), | ||
| is_public=( | ||
| OptionalState.update(self.is_public) | ||
| if self.is_public is not None | ||
| else OptionalState.nop() | ||
| ), |
Copilot
AI
Jan 7, 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 manual is not None check is incorrect for handling GraphQL optional fields. When a GraphQL optional field is not provided, it has the value Undefined (not None), but this check will still evaluate to True for Undefined values, causing unintended updates.
The codebase has a consistent pattern of using OptionalState.from_graphql() which properly handles Undefined (not provided) vs actual values. This should be:
status_spec = ScalingGroupStatusUpdaterSpec(
is_active=OptionalState.from_graphql(self.is_active),
is_public=OptionalState.from_graphql(self.is_public),
)The same pattern is used correctly throughout the codebase, for example in container_registry.py and endpoint.py.
| is_active=( | |
| OptionalState.update(self.is_active) | |
| if self.is_active is not None | |
| else OptionalState.nop() | |
| ), | |
| is_public=( | |
| OptionalState.update(self.is_public) | |
| if self.is_public is not None | |
| else OptionalState.nop() | |
| ), | |
| is_active=OptionalState.from_graphql(self.is_active), | |
| is_public=OptionalState.from_graphql(self.is_public), |
| description=( | ||
| TriState.update(self.description) | ||
| if self.description is not None | ||
| else TriState.nop() | ||
| ), | ||
| ) | ||
| network_spec = ScalingGroupNetworkConfigUpdaterSpec( | ||
| wsproxy_addr=( | ||
| TriState.update(self.wsproxy_addr) | ||
| if self.wsproxy_addr is not None | ||
| else TriState.nop() | ||
| ), | ||
| wsproxy_api_token=( | ||
| TriState.update(self.wsproxy_api_token) | ||
| if self.wsproxy_api_token is not None | ||
| else TriState.nop() | ||
| ), | ||
| use_host_network=( | ||
| OptionalState.update(self.use_host_network) | ||
| if self.use_host_network is not None | ||
| else OptionalState.nop() | ||
| ), |
Copilot
AI
Jan 7, 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 manual is not None check is incorrect for handling GraphQL optional fields. The description, wsproxy_addr, and wsproxy_api_token fields can be explicitly set to null in GraphQL (which should nullify the field) or not provided at all (which should leave it unchanged).
Use TriState.from_graphql() which properly handles all three states:
Undefined(field not provided) →nop()(no change)None(explicitly set to null) →nullify()(set to null)- Actual value →
update(value)(update with value)
This should be:
metadata_spec = ScalingGroupMetadataUpdaterSpec(
description=TriState.from_graphql(self.description),
)
network_spec = ScalingGroupNetworkConfigUpdaterSpec(
wsproxy_addr=TriState.from_graphql(self.wsproxy_addr),
wsproxy_api_token=TriState.from_graphql(self.wsproxy_api_token),
use_host_network=OptionalState.from_graphql(self.use_host_network),
)This pattern is consistently used throughout the codebase in similar mutations.
| description=( | |
| TriState.update(self.description) | |
| if self.description is not None | |
| else TriState.nop() | |
| ), | |
| ) | |
| network_spec = ScalingGroupNetworkConfigUpdaterSpec( | |
| wsproxy_addr=( | |
| TriState.update(self.wsproxy_addr) | |
| if self.wsproxy_addr is not None | |
| else TriState.nop() | |
| ), | |
| wsproxy_api_token=( | |
| TriState.update(self.wsproxy_api_token) | |
| if self.wsproxy_api_token is not None | |
| else TriState.nop() | |
| ), | |
| use_host_network=( | |
| OptionalState.update(self.use_host_network) | |
| if self.use_host_network is not None | |
| else OptionalState.nop() | |
| ), | |
| description=TriState.from_graphql(self.description), | |
| ) | |
| network_spec = ScalingGroupNetworkConfigUpdaterSpec( | |
| wsproxy_addr=TriState.from_graphql(self.wsproxy_addr), | |
| wsproxy_api_token=TriState.from_graphql(self.wsproxy_api_token), | |
| use_host_network=OptionalState.from_graphql(self.use_host_network), |
| driver_spec = ScalingGroupDriverConfigUpdaterSpec( | ||
| driver=( | ||
| OptionalState.update(self.driver) | ||
| if self.driver is not None | ||
| else OptionalState.nop() | ||
| ), | ||
| driver_opts=( | ||
| OptionalState.update(self.driver_opts) | ||
| if self.driver_opts is not None | ||
| else OptionalState.nop() | ||
| ), | ||
| ) | ||
| scheduler_spec = ScalingGroupSchedulerConfigUpdaterSpec( | ||
| scheduler=( | ||
| OptionalState.update(self.scheduler) | ||
| if self.scheduler is not None | ||
| else OptionalState.nop() | ||
| ), | ||
| scheduler_opts=( | ||
| OptionalState.update(ScalingGroupOpts.from_json(self.scheduler_opts)) | ||
| if self.scheduler_opts is not None | ||
| else OptionalState.nop() | ||
| ), | ||
| ) |
Copilot
AI
Jan 7, 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 manual is not None check is incorrect for handling GraphQL optional fields. The driver, driver_opts, scheduler, and scheduler_opts fields should use OptionalState.from_graphql() to properly distinguish between field not provided (Undefined) and field explicitly set.
This should be:
driver_spec = ScalingGroupDriverConfigUpdaterSpec(
driver=OptionalState.from_graphql(self.driver),
driver_opts=OptionalState.from_graphql(self.driver_opts),
)
scheduler_spec = ScalingGroupSchedulerConfigUpdaterSpec(
scheduler=OptionalState.from_graphql(self.scheduler),
scheduler_opts=OptionalState.from_graphql(
ScalingGroupOpts.from_json(self.scheduler_opts)
if self.scheduler_opts is not None
else None
),
)Note: For scheduler_opts, the value transformation to ScalingGroupOpts.from_json() needs to be done before passing to from_graphql(), but only when the value is not None.
| OptionalState.update(self.driver) | ||
| if self.driver is not None | ||
| else OptionalState.nop() |
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.
Could you take a look? We were using Undefined from GraphQL in that nop operation. @jopemachine
ed67ccc to
16f0f75
Compare
resolves #7523 (BA-3516)
Checklist: (if applicable)
ai.backend.testdocsdirectory