Skip to content

Add HypervisorReadyController to centralize Ready condition logic#286

Open
notandy wants to merge 2 commits intomainfrom
feature/ready-controller
Open

Add HypervisorReadyController to centralize Ready condition logic#286
notandy wants to merge 2 commits intomainfrom
feature/ready-controller

Conversation

@notandy
Copy link
Copy Markdown
Contributor

@notandy notandy commented Apr 8, 2026

Introduces a new ReadyController that computes the Ready condition based on other conditions (Onboarding, Offboarded, Terminating, Evicting) and the maintenance spec field. This greatly simplifies the ready logic and removes the need to update two status conditions at once in most controllers.

Also this is the first commit that moves the controller to it's own directory. The reason is that the vast amount of controllers polluting their common module namespace.

The ReadyController uses a StatusChangedPredicate to only reconcile when status changes (with exception of the maintenance spec), avoiding unnecessary reconciliation on spec changes.

Ready condition priority:

  1. Offboarded=True -> Ready=False
  2. Terminating=True -> Ready=False
  3. Onboarding in progress -> Ready=False
  4. Maintenance mode (with eviction state) -> Ready=False
  5. Onboarding not succeeded -> Ready=False
  6. All checks pass -> Ready=True

Summary by CodeRabbit

  • New Features

    • Added a dedicated Ready controller that centralizes computation and reconciliation of the Hypervisor Ready condition.
  • Refactor

    • Ready is now managed by the new controller; other controllers no longer toggle Ready directly.
  • Behavioral Changes

    • Onboarding, offboarding, maintenance, eviction and terminating flows now update their own conditions; Ready is derived by the centralized logic.
  • Tests

    • Added comprehensive tests covering Ready computation and status-change event filtering.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

A new ready controller centralizes computation and patching of the Hypervisor Ready condition; existing controllers stop mutating Ready and instead update their domain-specific conditions (onboarding, offboarding, terminating, maintenance, eviction) which the ready controller observes to set Ready.

Changes

Cohort / File(s) Summary
Ready controller + tests
internal/controller/ready/controller.go, internal/controller/ready/controller_test.go
Added a controller that computes ConditionTypeReady (ComputeReadyCondition), a StatusChangedPredicate to trigger reconciles on status and spec.maintenance changes, status-patching with field owner, and unit tests covering predicate and readiness decision logic.
Manager wiring
cmd/main.go
Imported and registered the new ready.Controller during manager startup; exits on setup failure.
Hypervisor core reconciler
internal/controller/hypervisor_controller.go
Stopped modifying Ready during Node terminating handling; now only updates Terminating condition (leaves existing Ready unchanged).
Maintenance reconciler
internal/controller/hypervisor_maintenance_controller.go
Removed explicit Ready updates in maintenance/ejection paths; still updates Evicted and Evicting conditions.
Offboarding reconciler
internal/controller/offboarding_controller.go
Reconciler gating now uses ConditionTypeOffboarded; helpers set/mark ConditionTypeOffboarded (False/True) instead of toggling Ready.
Onboarding reconciler
internal/controller/onboarding_controller.go
Onboarding flow no longer sets/clears ConditionTypeReady; it updates only onboarding-specific conditions (start, abort, complete).

Sequence Diagram(s)

sequenceDiagram
  participant Manager as "Manager\n(ctrl.Manager)\nrgba(52,152,219,0.5)"
  participant ReadyCtrl as "Ready Controller\nrgba(46,204,113,0.5)"
  participant API as "Kubernetes API\n(Hypervisor CR)\nrgba(241,196,15,0.5)"

  Manager->>ReadyCtrl: Start & register controller
  ReadyCtrl->>API: Watch Hypervisor events (status/spec.maintenance)
  Note over ReadyCtrl,API: On relevant event (status change or maintenance spec)
  ReadyCtrl->>API: GET Hypervisor
  ReadyCtrl->>ReadyCtrl: ComputeReadyCondition(hv)
  ReadyCtrl->>API: PATCH hv.status (fieldOwner: "ready") if changed
  API-->>ReadyCtrl: Updated status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • toanju
  • bashar-alkhateeb

Poem

🐰 I hopped through status, sniffed every field,

Offboarded, evicting, and onboarding revealed;
One little controller keeps the Ready gate,
Patching with care so states sync up straight —
A carrot for tests, a joyful hop to celebrate!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing a new ReadyController to centralize Ready condition logic across multiple controllers.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ready-controller

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/ready/controller.go`:
- Around line 134-163: The onboarding vs maintenance precedence is inverted:
move the active-onboarding branch (the check onboardingCondition.Status ==
metav1.ConditionTrue that returns ConditionReasonOnboarding/"Onboarding in
progress") so it executes before the maintenance handling, then leave the
nil/aborted/non-succeeded onboarding cases after the maintenance block; update
the logic in the handler around meta.FindStatusCondition(hv.Status.Conditions,
kvmv1.ConditionTypeOnboarding) to only short-circuit for Status==True early and
then, after the maintenance checks, evaluate onboardingCondition == nil,
onboardingCondition.Reason == kvmv1.ConditionReasonAborted, and the generic
non-succeeded case and return the appropriate metav1.Condition with Reason
Onboarding/Onboarding aborted/Onboarding not started as a table of explicit
combinations; apply the same reordering and case table changes to the analogous
block referenced later (the block around lines 166-203).
- Around line 57-73: StatusChangedPredicate.Update currently only compares
oldHv.Status vs newHv.Status so changes to hv.Spec.Maintenance don't trigger
reconcile and Ready can become stale; update StatusChangedPredicate.Update to
also detect changes to hv.Spec.Maintenance (e.g., return true if
!DeepEqual(oldHv.Status, newHv.Status) || oldHv.Spec.Maintenance !=
newHv.Spec.Maintenance) so that changes affecting ComputeReadyCondition (which
reads hv.Spec.Maintenance) will enqueue a reconcile.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: deeceb2d-145c-4aa5-bd78-6018bdec6e52

📥 Commits

Reviewing files that changed from the base of the PR and between 499dcef and 14b9d96.

📒 Files selected for processing (7)
  • cmd/main.go
  • internal/controller/hypervisor_controller.go
  • internal/controller/hypervisor_maintenance_controller.go
  • internal/controller/offboarding_controller.go
  • internal/controller/onboarding_controller.go
  • internal/controller/ready/controller.go
  • internal/controller/ready/controller_test.go
💤 Files with no reviewable changes (3)
  • internal/controller/hypervisor_controller.go
  • internal/controller/hypervisor_maintenance_controller.go
  • internal/controller/onboarding_controller.go

@notandy notandy force-pushed the feature/ready-controller branch from 14b9d96 to a9e2a9f Compare April 8, 2026 20:04
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/controller/ready/controller.go (1)

128-128: Consider using a constant for the "Offboarded" reason.

All other condition reasons use kvmv1.ConditionReason* constants, but line 128 uses a hardcoded string "Offboarded". For consistency and maintainability, consider defining and using a constant like kvmv1.ConditionReasonOffboarded.

♻️ Suggested change
 	if meta.IsStatusConditionTrue(hv.Status.Conditions, kvmv1.ConditionTypeOffboarded) {
 		return metav1.Condition{
 			Type:    kvmv1.ConditionTypeReady,
 			Status:  metav1.ConditionFalse,
-			Reason:  "Offboarded",
+			Reason:  kvmv1.ConditionReasonOffboarded, // define this constant in api/v1
 			Message: "Hypervisor has been offboarded",
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/ready/controller.go` at line 128, Replace the hardcoded
Reason string "Offboarded" in the Ready controller with a named constant to
match existing condition reasons; specifically, change the literal at the Reason
field (currently "Offboarded") to use kvmv1.ConditionReasonOffboarded (or add
that constant in kvmv1 if it doesn't exist) so the Ready controller's condition
reasons are consistent with other uses of kvmv1.ConditionReason*.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/controller/ready/controller.go`:
- Line 128: Replace the hardcoded Reason string "Offboarded" in the Ready
controller with a named constant to match existing condition reasons;
specifically, change the literal at the Reason field (currently "Offboarded") to
use kvmv1.ConditionReasonOffboarded (or add that constant in kvmv1 if it doesn't
exist) so the Ready controller's condition reasons are consistent with other
uses of kvmv1.ConditionReason*.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3874226e-7c21-4942-9e52-c1a84b6a3d24

📥 Commits

Reviewing files that changed from the base of the PR and between 14b9d96 and a9e2a9f.

📒 Files selected for processing (7)
  • cmd/main.go
  • internal/controller/hypervisor_controller.go
  • internal/controller/hypervisor_maintenance_controller.go
  • internal/controller/offboarding_controller.go
  • internal/controller/onboarding_controller.go
  • internal/controller/ready/controller.go
  • internal/controller/ready/controller_test.go
💤 Files with no reviewable changes (3)
  • internal/controller/hypervisor_controller.go
  • internal/controller/onboarding_controller.go
  • internal/controller/hypervisor_maintenance_controller.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/offboarding_controller.go

Introduces a new ReadyController that computes the Ready condition
based on other conditions (Onboarding, Offboarded, Terminating, Evicting)
and the maintenance spec field.

Changes:
- Add internal/controller/ready package with controller and tests
- Remove Ready condition setting from OnboardingController
- Remove Ready condition setting from MaintenanceController
- Remove Ready condition setting from OffboardingController
- Remove Ready condition setting from HypervisorController
- Update OffboardingController to use Offboarded condition for state tracking

The ReadyController uses a predicate that triggers on:
- Status changes
- Maintenance field changes (since it affects Ready condition)

Ready condition priority:
1. Offboarded=True -> Ready=False
2. Terminating=True -> Ready=False
3. Onboarding in progress (Status=True) -> Ready=False
4. Maintenance mode (with eviction state) -> Ready=False
5. Onboarding not started/aborted/not succeeded -> Ready=False
6. All checks pass -> Ready=True
@notandy notandy force-pushed the feature/ready-controller branch from a9e2a9f to b5addc7 Compare April 8, 2026 20:15
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/controller/ready/controller.go (1)

57-81: Ignore Ready-only status churn in Update().

This compare is broader than the controller’s actual inputs, and it also self-triggers on the Ready patch this controller writes. That guarantees one extra no-op reconcile after every successful Ready update. Please compare only the fields ComputeReadyCondition() reads, or at least exclude ConditionTypeReady, so the predicate matches the controller’s real dependencies. A small unit case for a Ready-only status diff would help lock that in.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/ready/controller.go` around lines 57 - 81, The Update()
in StatusChangedPredicate currently treats any status change as a trigger,
causing self-triggering reconciles when this controller writes the Ready
condition; modify StatusChangedPredicate.Update to only consider the fields used
by ComputeReadyCondition() (or at minimum ignore ConditionTypeReady) when
comparing oldHv.Status vs newHv.Status: e.g. create copies of oldHv.Status and
newHv.Status with the Ready condition removed/filtered and use
equality.Semantic.DeepEqual on those copies, while still keeping the existing
check for oldHv.Spec.Maintenance != newHv.Spec.Maintenance; also add a unit test
that constructs an UpdateEvent where only the Ready condition differs to assert
Update() returns false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/controller/ready/controller.go`:
- Around line 57-81: The Update() in StatusChangedPredicate currently treats any
status change as a trigger, causing self-triggering reconciles when this
controller writes the Ready condition; modify StatusChangedPredicate.Update to
only consider the fields used by ComputeReadyCondition() (or at minimum ignore
ConditionTypeReady) when comparing oldHv.Status vs newHv.Status: e.g. create
copies of oldHv.Status and newHv.Status with the Ready condition
removed/filtered and use equality.Semantic.DeepEqual on those copies, while
still keeping the existing check for oldHv.Spec.Maintenance !=
newHv.Spec.Maintenance; also add a unit test that constructs an UpdateEvent
where only the Ready condition differs to assert Update() returns false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8db087cb-c18e-471a-881e-486984855603

📥 Commits

Reviewing files that changed from the base of the PR and between a9e2a9f and b5addc7.

📒 Files selected for processing (7)
  • cmd/main.go
  • internal/controller/hypervisor_controller.go
  • internal/controller/hypervisor_maintenance_controller.go
  • internal/controller/offboarding_controller.go
  • internal/controller/onboarding_controller.go
  • internal/controller/ready/controller.go
  • internal/controller/ready/controller_test.go
💤 Files with no reviewable changes (3)
  • internal/controller/hypervisor_controller.go
  • internal/controller/onboarding_controller.go
  • internal/controller/hypervisor_maintenance_controller.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/offboarding_controller.go

Since the ReadyController now owns the Ready condition, update tests
for maintenance, offboarding, onboarding, and hypervisor controllers
to only check for the conditions those controllers actually set
(Evicting, Offboarded, Onboarding, Terminating) rather than the
computed Ready condition.

- Remove tests checking Ready condition from maintenance controller
- Update offboarding tests to check Offboarded condition instead
- Remove Ready condition checks from onboarding controller tests
- Remove test that checked Ready condition preservation in hypervisor
  controller (now handled by ReadyController)
- Set initial Offboarded condition in failure mode tests so reconcile
  hits the error paths
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 8, 2026

Merging this branch changes the coverage (1 decrease, 1 increase)

Impacted Packages Coverage Δ 🤖
github.com/cobaltcore-dev/openstack-hypervisor-operator/cmd 0.00% (ø)
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller 63.22% (-0.04%) 👎
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/ready 70.59% (+70.59%) 🌟

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/cobaltcore-dev/openstack-hypervisor-operator/cmd/main.go 0.00% (ø) 0 0 0
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/hypervisor_controller.go 77.27% (-0.67%) 66 (-2) 51 (-2) 15 👎
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/hypervisor_maintenance_controller.go 75.64% (-1.19%) 78 (-4) 59 (-4) 19 👎
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/offboarding_controller.go 71.88% (ø) 64 46 18
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/onboarding_controller.go 55.83% (+0.55%) 240 (-6) 134 (-2) 106 (-4) 👍
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/ready/controller.go 70.59% (+70.59%) 51 (+51) 36 (+36) 15 (+15) 🌟

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/hypervisor_controller_test.go
  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/hypervisor_maintenance_controller_test.go
  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/offboarding_controller_test.go
  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/onboarding_controller_test.go
  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/ready/controller_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants