Skip to content

Rework Onboarding#307

Open
fwiesel wants to merge 20 commits into
ssafrom
rework-onboarding
Open

Rework Onboarding#307
fwiesel wants to merge 20 commits into
ssafrom
rework-onboarding

Conversation

@fwiesel

@fwiesel fwiesel commented May 19, 2026

Copy link
Copy Markdown
Contributor

Refines the onboarding controller on top of the SSA migration (ssa branch, #330).

Changes:

  • Retry visibility: when createOrGetTestServer fails, the error is surfaced in the Onboarding condition instead of returning a bare error. This makes each retry visible to operators; previously the condition message was frozen even though reconciliation was healthy.
  • Server IDs in messages: error messages in the smoke-test flow (ERROR state, console-output failure, timeout, terminate failure) now include the server UUID. Each retry creates a new instance with a new UUID, so the condition message changes on every cycle — preventing SetApplyConfigurationStatusCondition from treating successive failures as no-ops.

Depends on: ssa (#330).

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ecc9eaf-a161-46eb-8ca2-8050a47b88c2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR switches onboarding condition updates to Kubernetes Server-Side Apply, adds SSA condition utilities, and updates onboarding controller flows and tests to use the new status-writing path.

Changes

Onboarding Controller SSA Refactor

Layer / File(s) Summary
Condition utilities for SSA apply-configurations
internal/utils/conditions.go, internal/utils/conditions_test.go
New utilities convert metav1.Condition values into apply-configuration conditions and update them by type, with tests covering conversion, append/update behavior, and transition time handling.
Controller imports and Nova lookup helper
internal/controller/onboarding_controller.go
SSA apply-configuration imports are added, and Nova property lookup now validates hostname labels, queries by hostname prefix, and returns IDs without mutating status.
SSA status helper and onboarding transitions
internal/controller/onboarding_controller.go
applyOnboardingCondition centralizes SSA status writes; reconcile, abort, initial onboarding, and completion paths now apply onboarding conditions through SSA.
Smoke test failure handling and server cleanup
internal/controller/onboarding_controller.go
smokeTest now writes SSA conditions for creation, ERROR, console output, timeout, and termination failures; createOrGetTestServer has a small cleanup-flow adjustment.
Controller test coverage updates
internal/controller/onboarding_controller_test.go
Tests now allow overriding server creation behavior and cover creation failure, ERROR-state handling, and timeout message assertions that include the server ID.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: notandy

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related to the PR, but it is too vague to convey the main change. Use a specific title like "Migrate onboarding controller to SSA status updates".
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rework-onboarding

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/utils/conditions_test.go`:
- Around line 30-31: The helper function ptr[T any](v T) *T in the tests is
unused and causing golangci-lint failures; remove the entire ptr function
declaration (symbol "ptr") from internal/utils/conditions_test.go so it is no
longer compiled, then run the test/lint to confirm the error is gone.

In `@internal/utils/conditions.go`:
- Around line 61-63: The loop that compares condition types dereferences
newCondition.Type without checking it, which can panic; update the loop in the
function using variables conditions, newCondition and existing so you first
check that newCondition.Type != nil (and optionally (*conditions)[i].Type !=
nil) before comparing *(*conditions)[i].Type == *newCondition.Type, returning or
skipping the comparison when newCondition.Type is nil to avoid the dereference.
- Around line 33-39: The condition conversion currently builds
k8sacmetav1.ConditionApplyConfiguration without copying ObservedGeneration, so
update the struct literal that creates k8sacmetav1.ConditionApplyConfiguration
(where result[i] = k8sacmetav1.ConditionApplyConfiguration{...}) to include
ObservedGeneration: &c.ObservedGeneration; also add/adjust the test that
constructs a metav1.Condition to assert the ObservedGeneration is preserved
after conversion so the omission is covered by tests.
🪄 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: 6f6306d2-89fd-46ac-b2d2-711ee3393c78

📥 Commits

Reviewing files that changed from the base of the PR and between 8854ff6 and 311d8e2.

📒 Files selected for processing (4)
  • internal/controller/onboarding_controller.go
  • internal/controller/onboarding_controller_test.go
  • internal/utils/conditions.go
  • internal/utils/conditions_test.go

Comment thread internal/utils/conditions_test.go Outdated
Comment thread internal/utils/conditions.go Outdated
Comment thread internal/utils/conditions.go
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 45 days with no activity.

@github-actions github-actions Bot added the stale label Jul 4, 2026
@fwiesel fwiesel force-pushed the rework-onboarding branch from 311d8e2 to 9980b46 Compare July 8, 2026 13:06
@fwiesel fwiesel changed the base branch from main to ssa July 8, 2026 13:49
@fwiesel fwiesel force-pushed the rework-onboarding branch from 9980b46 to ef9b6f1 Compare July 8, 2026 15:06
@github-actions github-actions Bot removed the stale label Jul 9, 2026
@fwiesel fwiesel force-pushed the rework-onboarding branch 2 times, most recently from f757216 to b6f16a2 Compare July 9, 2026 11:04
@fwiesel fwiesel force-pushed the rework-onboarding branch 2 times, most recently from aeb6791 to 40cd7f2 Compare July 9, 2026 18:30
fwiesel added 14 commits July 13, 2026 12:43
Replace MergeFromWithOptimisticLock + retry with Status().Apply() for
all eviction status updates. Introduces ConditionsFromStatus and
SetApplyConfigurationStatusCondition helpers to utils — mirroring
meta.SetStatusCondition semantics — as the foundation for all SSA
condition management. The evictionStatusCfg helper seeds every apply
with the full set of owned fields so SSA never prunes values between
reconcile cycles.
Replace PatchHypervisorStatusWithRetry with Status().Apply() for the
InternalIP scalar and ConditionTypeTerminating condition. Re-fetch the
hypervisor after the status apply so the subsequent spec patch sees a
fresh resourceVersion.
Replace PatchHypervisorStatusWithRetry with Status().Apply() for the
ConditionTypeTainted condition.
Replace PatchHypervisorStatusWithRetry with Status().Apply() for
ConditionTypeHypervisorDisabled, ConditionTypeEvicting, and the Evicted
scalar. ConditionTypeEvicting is removed by omitting it from the apply
config — SSA prunes map-list entries no longer claimed by the sole owner.
Replace PatchHypervisorStatusWithRetry with Status().Apply(). The Nova
ID lookup is inlined into Reconcile and combined with the initial
condition in a single apply, avoiding two applies from the same field
manager in one reconcile cycle which would cause SSA to prune the IDs.
HypervisorID and ServiceID are included in every subsequent apply to
retain ownership.
Replace PatchHypervisorStatusWithRetry with Status().Apply() for the
ConditionTypeOffboarded condition.
Replace PatchHypervisorStatusWithRetry with Status().Apply() for
ConditionTypeAggregatesUpdated and the Aggregates slice. The error path
includes the current Aggregates in the apply to prevent SSA from pruning
previously-set values on a transient OpenStack error. Clearing aggregates
to an empty slice uses a targeted merge patch to work around the omitempty
limitation in the generated apply configuration type.
Replace PatchHypervisorStatusWithRetry with Status().Apply() for
ConditionTypeTraitsUpdated and the Traits slice. Error paths pass the
current hv.Status.Traits to retain ownership and avoid SSA releasing
it on transient placement API failures.
…rStatusWithRetry

Replace the last caller of PatchHypervisorStatusWithRetry with
Status().Apply() for ConditionTypeReady. With no remaining callers,
PatchHypervisorStatusWithRetry and StatusPatchBackoff are removed.
Replace Create + SetControllerReference with Apply for the Eviction CR.
The owner reference and labels are set in the apply configuration
metadata, and SSA handles the upsert. A Get is still performed after the
apply to read the current eviction status conditions.
Replace CreateOrUpdate + SetOwnerReference with Apply for the cert-manager
Certificate CR. The owner reference is set in the apply configuration
metadata. The retry-on-conflict loop is no longer needed since SSA handles
concurrent updates correctly. The controller now applies the full
certificate spec on every reconcile so IP/DNS changes are picked up
immediately.
…ctions

With SSA the status config is built once in Reconcile and passed to
reconcileComputeService and reconcileEviction which mutate it directly.
This eliminates the intermediate condition return values and makes the
single-apply-per-reconcile explicit. The early-return guard for
already-enabled/disabled state is inlined into each branch.
With SSA the status config is built once at the top of Reconcile and
threaded through to all sub-functions (abortOnboarding, initialOnboarding,
smokeTest, completeOnboarding) which mutate it directly. An apply closure
is also passed so each function calls apply() when it has determined the
desired condition, rather than building and applying the config itself.
This removes the applyOnboardingCondition helper and makes the flow fully
declarative: compute desired state, then apply once.
conditions.go (cherry-pick from rework-onboarding):
- ConditionsFromStatus: preserve ObservedGeneration; the taint controller's
  skip-check gates on it and it was silently dropped.
- SetApplyConfigurationStatusCondition: guard against nil/empty Type to
  prevent a panic when a caller omits the Type field.
- SetApplyConfigurationStatusCondition: propagate ObservedGeneration when
  updating an existing condition entry.

hypervisor_controller.go:
- Do not call WithInternalIP("") when the node has no NodeInternalIP address
  yet (e.g. during kubelet restart or bootstrap). WithInternalIP combined with
  ForceOwnership would overwrite a previously stored valid IP with an empty
  string.
- Remove unnecessary metav1.ConditionStatus() conversion (unconvert lint).

eviction_controller.go:
- Remove extra blank line flagged by gofmt.

Note: the 3 pre-existing test failures in hypervisor_maintenance_controller_test.go
and hypervisor_controller_test.go are unrelated to these changes.
fwiesel added 6 commits July 13, 2026 12:46
With +listType=map / +listMapKey=type on HypervisorStatus.Conditions and
EvictionStatus.Conditions, the API server merges the conditions list by the
'type' key on Apply. This means:
- Conditions owned by other field managers are untouched (correct).
- Conditions previously written by this field manager that are absent from
  the Apply payload are pruned (also correct — used to delete conditions).

The previous ConditionsFromStatus helper round-tripped ALL conditions from
status into the apply payload. This was wrong: it caused each controller to
claim field ownership over conditions it does not own, interfering with other
controllers on the next Apply.

Replace ConditionsFromStatus with ConditionFromStatus (single condition),
and update every controller to seed only the condition(s) it owns before
calling SetApplyConfigurationStatusCondition. For controllers that need to
delete a condition by omission (HypervisorMaintenanceController removing
ConditionTypeEvicting on MaintenanceUnset), the owned conditions are seeded
selectively so the target condition is simply absent from the payload.

No behaviour change for the eviction_controller: it is the sole owner of
all conditions on the Eviction CR, so seeding all three types is correct.
Cluster-scoped resources (Hypervisor, Eviction) now take only a name
parameter in their apply configuration constructors. Remove the
previously required but always-empty namespace argument.
HypervisorController: the AgentPodsEvicted=False branch was returning
early with a requeue before running syncLabelsAndAnnotations, so node
label changes were not propagated to the Hypervisor while terminating.
Move the requeue after the spec patch so labels stay in sync.

HypervisorMaintenanceController: when enabling a compute service, also
clear the forced_down flag. Otherwise, an earlier HA event that set
forced_down=true would leave the service reachable but treated as down.
…rWithManager

SetupWithManager was inlining the same ctrl.NewControllerManagedBy call
that registerWithManager already provides, leaving registerWithManager
unreachable and triggering the unused lint error.
When the test instance creation in the onboarding smoke test fails, the
controller used to return a bare error from Reconcile and never updated
the Onboarding condition. Operators saw a stale message frozen for days
while the loop kept silently retrying — combined with the SSA condition
helper preserving LastTransitionTime when Status does not change, the
resource looked stuck even though reconciliation was healthy.

This change:
-  In smokeTest, when createOrGetTestServer fails, set the Onboarding
  condition to Testing with the current error message and requeue with
  defaultWaitTime instead of returning the error. This makes each retry
  observable in the resource itself and matches the style used by every
  other smokeTest error path.
- Include the failed server's UUID in the ERROR-state,
  console-output-failure, timeout, and terminate-failure messages. A fresh
  attempt allocates a new instance UUID, so each retry yields a different
  message even when Nova keeps returning the same fault text preventing
  SetApplyConfigurationStatusCondition from treating
  successive failures as no-op updates.

- Tests: add Ginkgo specs covering POST /servers returning 500 and a
  server in ERROR state, asserting the condition message, the requeue,
  the absence of a returned error, and (for ERROR) that the delete is
  issued and the UUID is in the message.
Covers ConditionFromStatus (including ObservedGeneration preservation)
and SetApplyConfigurationStatusCondition (append, update, status-change,
LastTransitionTime preservation, no-op detection, multi-condition
isolation).
@github-actions

Copy link
Copy Markdown

⚠️ Note: Baseline coverage from ssa branch is not available (artifact may be expired). Showing current coverage for changed files only.

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller 69.74% (+69.74%) 🌟
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/utils 86.54% (+86.54%) 🌟

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/controller/onboarding_controller.go 60.53% (+60.53%) 228 (+228) 138 (+138) 90 (+90) 🌟

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/onboarding_controller_test.go
  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/utils/conditions_test.go
  • github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/utils/suite_test.go

@fwiesel fwiesel force-pushed the ssa branch 2 times, most recently from b2fd489 to ad93c0b Compare July 13, 2026 19:59
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.

1 participant