-
Notifications
You must be signed in to change notification settings - Fork 428
Fix concurrent Tier creation at the same priority #7405
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
Signed-off-by: Dyanngg <[email protected]>
33bcf24 to
25eb037
Compare
antoninbas
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.
started review, will continue later
Signed-off-by: Dyanngg <[email protected]>
2934159 to
123fb5a
Compare
Signed-off-by: Dyanngg <[email protected]>
antoninbas
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.
mostly small comments
| if existing, exists := t.pendingPriorities[priority]; exists { | ||
| // Check if the existing reservation has timed out | ||
| if time.Since(existing.createdAt) > t.creationTimeout { | ||
| klog.V(2).InfoS("Tier priority reservation has timed out, allowing new reservation", "priority", priority, "tier", existing.tierName) |
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.
Is there a reason for logging this as V(2) when the other logs (which seem to be of equal importance) are logged with V(4)?
| delete(t.pendingPriorities, priority) | ||
| klog.V(4).InfoS("Released priority reservation for Tier", "priority", priority, "tier", tierName) | ||
| } else { | ||
| klog.InfoS("Attempted to release priority for a different Tier", "priority", priority, "tier", tierName, "existingTier", existing.tierName) |
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 agree with this log, but can you think of an example situation where this could happen?
| func (v *NetworkPolicyValidator) Run(stopCh <-chan struct{}) { | ||
| // Start cleanup routine for expired reservations from the tierValidator | ||
| for _, tv := range v.tierValidators { | ||
| go tv.startCleanupRoutine(stopCh) |
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.
you call Run in a goroutine in s.AddPostStartHook, so you expect it to be blocking, so I wouldn't call tv.startCleanupRoutine in yet another goroutine
BTW, would it make sense to just replace v.tierValidators with a single v.tierValidator?
| if t.priorityTracker == nil { | ||
| klog.ErrorS(nil, "Priority tracker is nil, cannot start cleanup routine") | ||
| return | ||
| } |
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 would remove that, it shouldn't be possible by design
| case <-ticker.C: | ||
| t.priorityTracker.cleanupExpiredReservations() | ||
| case <-stopCh: | ||
| klog.InfoS("Stopping Tier priority tracker cleanup routine") |
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.
If you have a Stopping log, I think you should have a corresponding Starting log at the beginning of the function
| // OnTierCreate should be called when a new Tier is detected by the informer. | ||
| // This releases the priority reservation for the created Tier. | ||
| func (t *tierValidator) OnTierCreate(tier *crdv1beta1.Tier) { | ||
| if tier != nil && t.priorityTracker != nil { |
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.
these nilness checks seem overly defensive here, I think there is no confusion that these values cannot possibly be nil
| // The priorityTracker should always be available for tierValidator | ||
| if t.priorityTracker == nil { |
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.
ditto
|
|
||
| t.Run("CleanupExpiredReservations", func(t *testing.T) { | ||
| tracker := newTierPriorityTracker() | ||
| tracker.creationTimeout = 50 * time.Millisecond // Short timeout for testing |
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'd recommend injecting a fake clock here, like we do in similar unit tests
| tier2Name := "concurrent-tier-2" | ||
|
|
||
| // Use channels to coordinate simultaneous creation attempts | ||
| startCh := make(chan struct{}) |
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 don't think this channel provides much value compared to just staring the 2 goroutines and letting them call CreateTier right away.
| if err1 == nil && err2 == nil { | ||
| k8sUtils.DeleteTier(tier1Name) | ||
| k8sUtils.DeleteTier(tier2Name) | ||
| failOnError(fmt.Errorf("Tiers were both created with the same priority when applied simultaneously"), t) |
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.
This seems to be a convoluted use of failOnError, compared to just having t.Fail :)
Fixes #7309
Disclamier
This PR and its description are mostly generated through Cursor with the
claude-4-sonnetmodel. It took 6 tokens for me to prompt it the problem, provide context, challenge its initial solution (which did not work), improve on implementation details and add UT and e2e test. I have proof-read and modified the generated code as well before pushing for review. The fix is also tested in a local kind cluster to verify that concurrent Tier creation at the same priority is rejected.Tier Priority Race Condition Fix
Problem Description
The original Tier validating admission webhook had a race condition when checking for overlapping priorities. The issue occurred because:
mytier1with priority X → successmytier2with same priority X → success (informer not yet updated)mytier1is created in etcdmytier2is created with the same priority XThe key insight is that the webhook validation completes before the actual Kubernetes resource creation, creating a window where multiple tiers with the same priority can be validated simultaneously.
Solution: Priority Reservation Until Creation Completion
The solution implements a priority reservation system that reserves priorities during validation and only releases them when the Tier is actually created and detected by the informer.
Key Components
1.
tierPriorityTracker2. Priority Reservation Mechanism
3. Enhanced Validation Flow
How It Prevents the Race Condition
Before (Race Condition Possible)
After (Race Condition Prevented)
Implementation Details
1. Architecture
The
priorityTrackeris now a field of theresourceValidatorbase struct, but only initialized fortierValidator:2. Single Validation Method
The
tierValidatornow has a singlecreateValidatemethod that uses its ownpriorityTracker:3. Informer Integration
Priority reservations are released when the informer detects the actual Tier creation:
Benefits
Configuration
Timeout Adjustment
The validation timeout can be adjusted if needed:
Testing Scenarios
1. Concurrent Same Priority (Should Fail)
Expected: One succeeds, one fails with priority overlap error
2. Concurrent Different Priorities (Should Succeed)
Expected: Both succeed
3. Timeout Scenario
If a validation takes longer than 30 seconds (very unlikely), the waiting request will timeout and fail gracefully.
Migration Notes
This implementation provides a robust solution to the Tier priority race condition while maintaining backward compatibility and system performance.