refactor(capacity): convert timer-based controller to controller-runtime reconciler#1025
Conversation
|
Warning Review limit reached
Next review available in: 38 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe capacity controller now runs as a controller-runtime Reconciler with coalesced watches and a minimum reconcile interval. Configuration validation, manager startup wiring, deployment values, reconciliation receivers, and timing-focused tests were updated. ChangesCapacity Reconciler Refactor
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Manager
participant Watches
participant Reconciler
participant CapacityCRD
Manager->>Reconciler: SetupWithManager
Watches->>Reconciler: Coalesced event
Reconciler->>Reconciler: Check minimum interval
alt Interval not elapsed
Reconciler-->>Manager: Requeue remaining interval
else Interval elapsed
Reconciler->>CapacityCRD: Reconcile and update capacity
Reconciler-->>Manager: Requeue configured interval
end
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/scheduling/reservations/capacity/controller_test.go (1)
857-965: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated reconcile-test setup into a same-file helper.
TestReconcile_ReactsToKnowledgeChangeandTestReconcile_MinIntervalEarlyReturnshare almost identical setup (scheme, hypervisor, knowledge, fake client, mock scheduler,NewController). Factoring it into one same-file helper keeps the tests short and focused on their distinct assertions.As per coding guidelines: "Test files should be short and contain only necessary test cases" and "keep helper functions in the same file as the tests that use them".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/scheduling/reservations/capacity/controller_test.go` around lines 857 - 965, Both TestReconcile_ReactsToKnowledgeChange and TestReconcile_MinIntervalEarlyReturn duplicate the same controller test setup. Extract the repeated scheme, hypervisor, knowledge, fake client, mock scheduler, and NewController initialization into a same-file helper in controller_test.go, then have both tests call that helper and keep only their unique assertions. Use the existing identifiers newTestScheme, newHypervisor, newFlavorGroupKnowledge, newMockSchedulerServer, and NewController to make the helper easy to locate and reuse.Source: Coding guidelines
internal/scheduling/reservations/capacity/controller.go (1)
105-127: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: collapse the repeated watch registrations into a loop.
The four
WatchesMulticlustercalls differ only by object type and the name in the error message. A small slice-driven loop removes the copy/paste and makes adding future CRDs a one-line change.♻️ Sketch
watched := []struct { name string obj client.Object }{ {"Knowledge", &v1alpha1.Knowledge{}}, {"Hypervisor", &hv1.Hypervisor{}}, {"CommittedResource", &v1alpha1.CommittedResource{}}, {"Pipeline", &v1alpha1.Pipeline{}}, } for _, w := range watched { if bldr, err = bldr.WatchesMulticluster(w.obj, handler.EnqueueRequestsFromMapFunc(coalesce)); err != nil { return fmt.Errorf("failed to watch %s: %w", w.name, err) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/scheduling/reservations/capacity/controller.go` around lines 105 - 127, The controller setup repeats four nearly identical WatchesMulticluster registrations in the capacity controller’s builder chain, so collapse them into a small slice-driven loop. Use a local list of watched resources with a display name and client.Object, iterate in the same setup function, and keep the existing coalesce handler plus the per-resource error wrapping (for example, in BuildController and its WatchesMulticluster calls) so adding future CRDs only requires one entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/scheduling/reservations/capacity/controller_test.go`:
- Around line 857-965: Both TestReconcile_ReactsToKnowledgeChange and
TestReconcile_MinIntervalEarlyReturn duplicate the same controller test setup.
Extract the repeated scheme, hypervisor, knowledge, fake client, mock scheduler,
and NewController initialization into a same-file helper in controller_test.go,
then have both tests call that helper and keep only their unique assertions. Use
the existing identifiers newTestScheme, newHypervisor, newFlavorGroupKnowledge,
newMockSchedulerServer, and NewController to make the helper easy to locate and
reuse.
In `@internal/scheduling/reservations/capacity/controller.go`:
- Around line 105-127: The controller setup repeats four nearly identical
WatchesMulticluster registrations in the capacity controller’s builder chain, so
collapse them into a small slice-driven loop. Use a local list of watched
resources with a display name and client.Object, iterate in the same setup
function, and keep the existing coalesce handler plus the per-resource error
wrapping (for example, in BuildController and its WatchesMulticluster calls) so
adding future CRDs only requires one entry.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c8c19406-0b28-49f1-8690-e21b7012b6ee
📒 Files selected for processing (4)
cmd/manager/main.gointernal/scheduling/reservations/capacity/config.gointernal/scheduling/reservations/capacity/controller.gointernal/scheduling/reservations/capacity/controller_test.go
PhilippMatthes
left a comment
There was a problem hiding this comment.
Amazing job! I would appreciate if we could exchange on my comments a little -- maybe I'm also not grasping the full context. Let me know, we can also sync offline.
mblos
left a comment
There was a problem hiding this comment.
overall looks good, thanks. Mainly some small changes for the watch triggers suggested
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/scheduling/reservations/capacity/controller.go`:
- Around line 56-76: Align hvCapacityChangePredicate.DeleteFunc with the
AZ-label filtering used by CreateFunc and UpdateFunc: return true only when the
deleted object carries topology.kubernetes.io/zone, using the delete event’s
object labels. Preserve the existing unconditional behavior only if stale-label
handling is explicitly required.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 479fae29-cbba-4699-8f1c-0248bce7d99e
📒 Files selected for processing (3)
cmd/manager/main.gointernal/scheduling/reservations/capacity/config.gointernal/scheduling/reservations/capacity/controller.go
🚧 Files skipped from review as they are similar to previous changes (1)
- cmd/manager/main.go
Test Coverage ReportTest Coverage 📊: 70.2% |
## Changelog ### cortex v0.3.0 (sha-ee9cd485) Breaking changes: - `NumberOfObjects` field in `DatasourceStatus` changed from `int64` to `*int64` so `omitempty` no longer strips zero values ([#1024](#1024)) Non-breaking changes: - Migrate CR and failover calls to options-based pipeline selection — eliminates dedicated CR and failover pipelines by encoding behavioral differences as call-time scheduling options ([#950](#950)) - Convert capacity controller from timer-based to controller-runtime reconciler with reactive watches and coalesced reconcile keys ([#1025](#1025)) - Split quota enforcement `accept_skipped` decision into sub-buckets for granular observability ([#1022](#1022)) - Fix alert wording: 'LackingBehind' → 'LaggingBehind' ([#1031](#1031)) - Increase datasource lagging behind alert time frame to 30 min ([#1035](#1035)) - Add logger context for remote cluster communication ([#1023](#1023)) - Update `golang.org/x/sync` v0.21.0→v0.22.0, `golang.org/x/term` v0.44.0→v0.45.0 ([#1026](#1026)) - Update `ironcore` v0.4.2→v0.4.3 ([#1029](#1029)) - Update `github.com/sapcc/go-bits` ([#1030](#1030)) ### cortex-shim v0.1.6 (sha-ee9cd485) Includes updated image sha-ee9cd485. ### cortex-nova v0.0.80 Includes updated charts cortex v0.3.0, cortex-postgres v0.6.8. - Scope `SyncObjectsDroppedToZero` alert to only fire when previously non-zero ([#1024](#1024)) - Add `capacityMinReconcileInterval` config key for capacity controller ([#1025](#1025)) ### cortex-cinder v0.0.80 Includes updated charts cortex v0.3.0, cortex-postgres v0.6.8. - Scope `SyncObjectsDroppedToZero` alert to only fire when previously non-zero ([#1024](#1024)) ### cortex-manila v0.0.80 Includes updated charts cortex v0.3.0, cortex-postgres v0.6.8. - Scope `SyncObjectsDroppedToZero` alert to only fire when previously non-zero ([#1024](#1024)) ### cortex-crds v0.0.80 Includes updated chart cortex v0.3.0. ### cortex-ironcore v0.0.80 Includes updated chart cortex v0.3.0. ### cortex-pods v0.0.80 Includes updated chart cortex v0.3.0. ### cortex-placement-shim v0.1.6 Includes updated chart cortex-shim v0.1.6. ## Dependencies - Bump PR: #1038 (must be merged before this PR) - Changelog PR: #1039 (merge after this PR)
Summary
The
FlavorGroupCapacityCRDs were previously updated on a fixed 5-minute timer via a plain goroutine (Start()/manager.RunnableFunc). This had two problems: the first useful run only happened after the full interval because the Knowledge CRD isn't populated at startup, and there was no reactive trigger for changes to hypervisors, committed resources, or pipelines.This PR converts the capacity controller to a controller-runtime reconciler:
MinReconcileInterval(default 30s) — prevents back-to-back reconciles; early returns withRequeueAfter: remainingso no change is ever permanently missedReconcileInterval(default 5m) acts as the periodic floor, returned asRequeueAfterafter every successful runReconcileInterval > MinReconcileIntervalat startupreconcileAll()and everything below it is unchanged