-
Notifications
You must be signed in to change notification settings - Fork 165
feat(BA-3487): Implement ScalingGroup Domain Association Actions #7651
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
Conversation
AssociateScalingGroupWithDomain ActionAssociateScalingGroupWithDomain Action
55c7624 to
7f1e51d
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 implements the AssociateScalingGroupWithDomainAction as part of BA-3487, refactoring the domain association operations from direct SQL queries to the new action/processor pattern. The implementation adds action definitions, service methods, repository operations, and comprehensive test coverage at both service and repository levels. However, the PR only implements the Associate action and refactors only the plural mutation, leaving the singular mutation and the Disassociate action unimplemented despite the original issue requirements.
- Implements
AssociateScalingGroupWithDomainActionfollowing the action pattern - Refactors
AssociateScalingGroupsWithDomain(plural) mutation to use the new action - Adds comprehensive tests for service and repository layers
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/ai/backend/manager/services/scaling_group/actions/associate_with_domain.py |
Defines the action and result classes for associating a scaling group with a domain |
src/ai/backend/manager/services/scaling_group/service.py |
Adds service method to handle the association action |
src/ai/backend/manager/services/scaling_group/processors.py |
Registers the new action processor |
src/ai/backend/manager/repositories/scaling_group/repository.py |
Adds repository method for domain association |
src/ai/backend/manager/repositories/scaling_group/db_source/db_source.py |
Implements database operation for creating the association |
src/ai/backend/manager/repositories/scaling_group/creators.py |
Adds ScalingGroupForDomainCreatorSpec for building association rows |
src/ai/backend/manager/models/gql_models/scaling_group.py |
Refactors AssociateScalingGroupsWithDomain (plural) mutation to use the new action pattern |
tests/unit/manager/services/scaling_group/test_scaling_group_service.py |
Adds service layer tests for success and error propagation |
tests/unit/manager/repositories/scaling_group/test_scaling_group_repository.py |
Adds repository layer test with fixtures, updates cleanup logic for association table |
changes/7651.feature.md |
Changelog entry for the feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
changes/7651.feature.md
Outdated
| @@ -0,0 +1 @@ | |||
| Implement `AssociateScalingGroupWithDomainAction`, and repository methods | |||
Copilot
AI
Dec 31, 2025
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.
According to issue #7487, this PR should implement both AssociateScalingGroupWithDomainAction AND DisassociateScalingGroupWithDomainAction. However, only the Associate action has been implemented. The DisassociateScalingGroupWithDomain mutations (both singular and plural variants) still use the old simple_db_mutate pattern and have not been refactored to use the new action pattern.
| async def test_associate_scaling_group_with_domain_success( | ||
| self, | ||
| scaling_group_repository: ScalingGroupRepository, | ||
| db_with_cleanup: ExtendedAsyncSAEngine, | ||
| sample_scaling_group_for_association: str, | ||
| sample_domain: str, | ||
| ) -> None: | ||
| """Test associating a scaling group with a domain""" | ||
| creator = Creator( | ||
| spec=ScalingGroupForDomainCreatorSpec( | ||
| scaling_group=sample_scaling_group_for_association, | ||
| domain=sample_domain, | ||
| ) | ||
| ) | ||
| await scaling_group_repository.associate_scaling_group_with_domain(creator) | ||
|
|
||
| # Verify association | ||
| async with db_with_cleanup.begin_readonly_session() as db_sess: | ||
| result = await db_sess.execute( | ||
| sa.select(ScalingGroupForDomainRow).where( | ||
| sa.and_( | ||
| ScalingGroupForDomainRow.scaling_group | ||
| == sample_scaling_group_for_association, | ||
| ScalingGroupForDomainRow.domain == sample_domain, | ||
| ) | ||
| ) | ||
| ) | ||
| association = result.scalar_one_or_none() | ||
| assert association is not None | ||
| assert association.scaling_group == sample_scaling_group_for_association | ||
| assert association.domain == sample_domain |
Copilot
AI
Dec 31, 2025
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.
Consider adding a test case for the duplicate association scenario at the repository level. While the service layer tests the IntegrityError propagation, it would be valuable to have a repository test that verifies the database unique constraint (uq_sgroup_domain) works as expected when attempting to create a duplicate association. This would test the actual database behavior rather than just mocking it.
| async def associate_scaling_group_with_domain( | ||
| self, | ||
| creator: Creator[ScalingGroupForDomainRow], | ||
| ) -> None: | ||
| """Associates a single scaling group with a domain.""" | ||
| await self._db_source.associate_scaling_group_with_domain(creator) |
Copilot
AI
Dec 31, 2025
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 associate_scaling_group_with_domain method is missing the @scaling_group_repository_resilience.apply() decorator that is applied to all other repository methods (create_scaling_group, search_scaling_groups, purge_scaling_group). For consistency and to benefit from the retry and metric policies, this decorator should be added.
| ctx: GraphQueryContext = info.context | ||
| for scaling_group in scaling_groups: | ||
| creator = Creator( | ||
| spec=ScalingGroupForDomainCreatorSpec( | ||
| scaling_group=scaling_group, | ||
| domain=domain, | ||
| ) | ||
| ) | ||
| await ( | ||
| ctx.processors.scaling_group.associate_scaling_group_with_domain.wait_for_complete( | ||
| AssociateScalingGroupWithDomainAction(creator=creator) | ||
| ) | ||
| ) | ||
| return cls(ok=True, msg="") |
Copilot
AI
Dec 31, 2025
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 AssociateScalingGroupWithDomain mutation (singular) should also be refactored to use the new action pattern for consistency. Currently, this mutation still uses the old simple_db_mutate pattern (line 752-756 in the original code), while AssociateScalingGroupsWithDomain (plural) has been refactored to use the new action pattern. Both mutations should use the same implementation approach. The singular mutation should be refactored to use AssociateScalingGroupWithDomainAction just like the plural version now does.
changes/7651.feature.md
Outdated
| @@ -0,0 +1 @@ | |||
| Implement `AssociateScalingGroupWithDomainAction`, and repository methods | |||
Copilot
AI
Dec 31, 2025
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.
AssociateScalingGroupWithDomain Action9552aac to
4af738e
Compare
b47a35d to
6425a52
Compare
| db_with_cleanup: ExtendedAsyncSAEngine, | ||
| ) -> AsyncGenerator[str, None]: | ||
| """Create a sample domain for testing""" | ||
| domain_name = "test-domain-for-sgroup" |
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.
How about making domain name with random uuid to avoid conflict?
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.
Since all test fixtures are isolated, I think there doesn’t seem to be any need to worry about conflicts.
d61a125 to
c60f0d0
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.
Could you explain in what situations those newly added Repository methods should be used? @jopemachine
There are lots of association, disassociation legacy GQL mutations. I will refactor them with this actions @HyeockJinKim backend.ai/src/ai/backend/manager/api/gql_legacy/schema.py Lines 417 to 442 in 9bc6db4
|
|
- Add DisassociateScalingGroupWithDomainAction using Purger pattern - Add ScalingGroupForDomainPurgerSpec for batch deletion - Update db_source, repository, and service layer - Register processor for disassociate action - Update GQL mutations to use action-processor pattern - Add unit tests for repository and service 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Reverts the GQL mutation changes for associate/disassociate scaling group with domain to use the original simple_db_mutate pattern instead of the action-processor pattern, keeping only the underlying service layer with the action-processor pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
15c6cc6 to
91a9e87
Compare
| async def test_associate_scaling_group_with_domains_repository_error_propagates( | ||
| self, | ||
| scaling_group_service: ScalingGroupService, | ||
| mock_repository: MagicMock, | ||
| ) -> None: | ||
| """Test that repository errors propagate through the service for association""" | ||
| mock_repository.associate_scaling_group_with_domains = AsyncMock( | ||
| side_effect=IntegrityError("statement", {}, Exception("Duplicate entry")) | ||
| ) | ||
|
|
||
| bulk_creator: BulkCreator[ScalingGroupForDomainRow] = BulkCreator( | ||
| specs=[ | ||
| ScalingGroupForDomainCreatorSpec( | ||
| scaling_group="test-sgroup", | ||
| domain="test-domain", | ||
| ) | ||
| ] | ||
| ) | ||
| action = AssociateScalingGroupWithDomainsAction(bulk_creator=bulk_creator) | ||
|
|
||
| with pytest.raises(IntegrityError): | ||
| await scaling_group_service.associate_scaling_group_with_domains(action) |
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.
Why was this deleted?
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.
I removed it because it felt like an obvious assumption.
resolves #7487 (BA-3487)
Checklist: (if applicable)
ai.backend.testdocsdirectory