Skip to content

Drop BMC Pending state in favor of ConditionReset - #1063

Open
afritzler wants to merge 2 commits into
ironcore-dev:mainfrom
afritzler:enh/bmc-pending-state
Open

Drop BMC Pending state in favor of ConditionReset#1063
afritzler wants to merge 2 commits into
ironcore-dev:mainfrom
afritzler:enh/bmc-pending-state

Conversation

@afritzler

@afritzler afritzler commented Jul 28, 2026

Copy link
Copy Markdown
Member

Proposed Changes

Drop BMC Pending state in favor of ConditionReset.

Summary by CodeRabbit

  • API Updates
    • Clarified that BMC status is reported by Redfish.
    • Removed the Pending default and fixed allowed-value constraints for status.state.
    • Updated API reference documentation to reflect the revised BMC state information.
  • Bug Fixes
    • Improved reset handling, status updates, readiness reporting, retries, and reset outcome conditions.
  • Documentation
    • Updated CLI help text to explain reconnection behavior after failures and during resets.

@afritzler
afritzler requested a review from a team as a code owner July 28, 2026 08:44
@github-actions github-actions Bot added api-change documentation Improvements or additions to documentation enhancement New feature or request labels Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The BMC state API now describes Redfish-reported values without enum validation or a Pending default. Reset reconciliation uses conditions for reset state, requeues while resets are active, and uses renamed reconciler configuration fields.

Changes

BMC state and reset handling

Layer / File(s) Summary
BMC state contract and generated schemas
api/v1alpha1/..., config/crd/..., docs/api-reference/api.md
BMC state annotations, constants, generated schemas, CRD definitions, and API documentation remove the Pending default and Error/Pending enum values while describing state as reported by Redfish.
Reconciler configuration contract and wiring
internal/controller/bmc_controller.go, cmd/main.go, internal/controller/suite_test.go
Reconciler configuration fields and their constructor and test wiring use the renamed options, timeout, retry, failure-delay, and DNS template fields.
Reset reconciliation and error handling
internal/controller/bmc_controller.go, internal/controller/conditions.go, internal/controller/bmc_controller_test.go
Reconciliation requeues during resets, manages reset state through conditions, patches readiness and BMC details, and handles reset outcomes and Redfish errors directly.

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

Possibly related PRs

Suggested labels: size/L

Suggested reviewers: xkonni

Sequence Diagram(s)

sequenceDiagram
  participant BMCReconciler
  participant ConditionState
  participant RedfishBMC
  BMCReconciler->>ConditionState: Check ConditionReset
  BMCReconciler->>RedfishBMC: Request graceful reset
  RedfishBMC-->>BMCReconciler: Return reset result
  BMCReconciler->>ConditionState: Clear or retain reset condition
  BMCReconciler-->>BMCReconciler: Requeue during active reset
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: replacing the BMC Pending state with ConditionReset.
Description check ✅ Passed The description states the primary change and uses the required Proposed Changes heading, but it omits the template bullets and Fixes field.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

480-480: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Capitalize the propagated controller error.

The new error message starts with lowercase could; controller error messages should start with a capital letter and identify the object type.

🤖 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/controller/bmc_controller.go` at line 480, Update the error returned
in the BMC reset flow to begin with a capitalized message and explicitly
identify the BMC object type, while preserving the existing object name and
no-client-connection context.

Source: Coding guidelines

🤖 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/controller/bmc_controller.go`:
- Around line 480-490: Update resetBMC so ConditionReset is cleared before
returning when no client connection is available or a permanent ResetManager
error occurs. Preserve ConditionReset only after reset initiation is confirmed
or when the operation is intentionally retryable, so shouldResetBMC can schedule
future attempts for pre-initiation failures.

---

Nitpick comments:
In `@internal/controller/bmc_controller.go`:
- Line 480: Update the error returned in the BMC reset flow to begin with a
capitalized message and explicitly identify the BMC object type, while
preserving the existing object name and no-client-connection context.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d3288ccd-d8c9-49b1-b326-8f6ce056d3f4

📥 Commits

Reviewing files that changed from the base of the PR and between e26ff2b and 58397ba.

⛔ Files ignored due to path filters (2)
  • dist/chart/templates/crd/bmcs.metal.ironcore.dev.yaml is excluded by !**/dist/**
  • dist/install.yaml is excluded by !**/dist/**
📒 Files selected for processing (6)
  • api/v1alpha1/applyconfiguration/api/v1alpha1/bmcstatus.go
  • api/v1alpha1/applyconfiguration/internal/internal.go
  • api/v1alpha1/bmc_types.go
  • config/crd/bases/metal.ironcore.dev_bmcs.yaml
  • docs/api-reference/api.md
  • internal/controller/bmc_controller.go
💤 Files with no reviewable changes (1)
  • api/v1alpha1/applyconfiguration/internal/internal.go

Comment thread internal/controller/bmc_controller.go Outdated
@afritzler
afritzler force-pushed the enh/bmc-pending-state branch from 58397ba to 7ad92b4 Compare July 28, 2026 13:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/bmc_controller.go (1)

128-141: 🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

Auto-reset path always passes a nil client to resetBMC.

bmcutils.GetBMCClientFromBMC returns (nil, err) on every failure path, so inside if err != nil the bmcClient handed to resetBMC at Line 132 is always nil. resetBMC then takes its nil-client branch: it sets ConditionReset=True, immediately clears it, and returns an error — the Redfish ResetManager call is never reached. The automatic reset feature (FailureResetDelay) therefore can never actually reset a BMC, and reconcile returns a hard error instead of the intended requeue at Lines 136-138.

A separate client (constructed without the connectivity gate, or via a dedicated reset connection) is needed before attempting ResetManager.

🤖 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/controller/bmc_controller.go` around lines 128 - 141, The auto-reset
branch in the reconcile flow must not pass the failed nil bmcClient from
GetBMCClientFromBMC into resetBMC. Before calling resetBMC, obtain a separate
BMC client without the connectivity-check gate (or use the dedicated reset
connection) and pass that client so resetBMC can reach Redfish ResetManager;
preserve the existing reset logging and requeue behavior on success.
🧹 Nitpick comments (3)
internal/controller/suite_test.go (1)

180-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

FailureResetDelay is left at zero, so the automatic-reset path is never exercised.

shouldResetBMC short-circuits when FailureResetDelay == 0, meaning no test in this suite covers the auto-reset branch (see the nil-client issue flagged in internal/controller/bmc_controller.go). Setting a short delay here would give coverage for that path.

🤖 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/controller/suite_test.go` around lines 180 - 196, Set a short
non-zero FailureResetDelay in the BMCReconciler initialization used by the suite
setup, alongside ResetWaitTime and ClientRetryInterval, so shouldResetBMC
exercises the automatic-reset branch while preserving the existing test timing
behavior.
internal/controller/bmc_controller.go (2)

474-494: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the if err == nil { ... } else { ... } flow.

The else branch after a return is flagged by revive/golint (indent-error-flow) and the trailing fallthrough to Line 493-494 is hard to follow. Inverting the check reads better and makes the "retryable 5xx" fallthrough explicit.

♻️ Proposed refactor
-	if err := bmcClient.ResetManager(ctx, bmcObj.Spec.BMCUUID, schemas.GracefulRestartResetType); err == nil {
-		log.Info("Successfully reset BMC via Redfish", "BMC", bmcObj.Name)
-		return nil
-	} else {
-		if httpErr, ok := errors.AsType[*schemas.Error](err); ok {
-			// only retryable on 5xx; anything else is a permanent failure for this attempt
-			if httpErr.HTTPReturnedStatusCode < 500 || httpErr.HTTPReturnedStatusCode >= 600 {
-				return errors.Join(
-					r.clearResetCondition(ctx, bmcObj),
-					fmt.Errorf("could not reset BMC: %w", err),
-				)
-			}
-		} else {
-			return errors.Join(
-				r.clearResetCondition(ctx, bmcObj),
-				fmt.Errorf("could not reset BMC, unknown error: %w", err),
-			)
-		}
-	}
+	err := bmcClient.ResetManager(ctx, bmcObj.Spec.BMCUUID, schemas.GracefulRestartResetType)
+	if err == nil {
+		log.Info("Successfully reset BMC via Redfish", "BMC", bmcObj.Name)
+		return nil
+	}
+	httpErr, ok := errors.AsType[*schemas.Error](err)
+	if !ok {
+		return errors.Join(
+			r.clearResetCondition(ctx, bmcObj),
+			fmt.Errorf("could not reset BMC, unknown error: %w", err),
+		)
+	}
+	// only retryable on 5xx; anything else is a permanent failure for this attempt
+	if httpErr.HTTPReturnedStatusCode < 500 || httpErr.HTTPReturnedStatusCode >= 600 {
+		return errors.Join(
+			r.clearResetCondition(ctx, bmcObj),
+			fmt.Errorf("could not reset BMC: %w", err),
+		)
+	}

As per coding guidelines, run make lint-fix and make test after editing Go source files.

🤖 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/controller/bmc_controller.go` around lines 474 - 494, Refactor the
reset handling around bmcClient.ResetManager to invert the err == nil check:
return immediately on success, then process non-nil errors without an else
branch. Preserve the existing permanent-failure handling, clearResetCondition
calls, and retryable 5xx fallthrough to the final log and nil return. Run make
lint-fix and make test after editing.

Source: Coding guidelines


395-411: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Don’t return the original failure error if you want ClientRetryInterval to apply.

At reconcile() line 140, this path returns both Result{RequeueAfter: r.ClientRetryInterval} and a non-nil error, so controller-runtime ignores the fixed retry delay and uses its default exponential backoff instead. If the intended behavior is a fixed retry cadence for BMC connection failures, set ready condition/status without returning the original error here.

🤖 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/controller/bmc_controller.go` around lines 395 - 411, Update
updateReadyConditionOnBMCFailure to return nil after successfully patching the
BMC Ready condition, rather than propagating the original err; preserve
returning the patch error so reconcile can retain the configured
ClientRetryInterval for BMC connection failures.
🤖 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 `@cmd/main.go`:
- Around line 432-434: The help text for --bmc-reset-resync-interval must
reflect that ClientRetryInterval controls retries for any BMC connection
failure, not only polling during an in-progress reset. Update the flag
description near its declaration to mention general BMC connection retry
behavior while preserving the existing 2-minute default.

In `@internal/controller/bmc_controller.go`:
- Around line 123-126: The BMC reset condition remains true after an
unsuccessful reset wait, preventing future automatic resets. Update the
reconciliation flow around waitForBMCReset and the failure path after the wait
window expires to clear ConditionReset when the BMC has not reconnected, while
preserving the existing successful-reset and retry behavior.

---

Outside diff comments:
In `@internal/controller/bmc_controller.go`:
- Around line 128-141: The auto-reset branch in the reconcile flow must not pass
the failed nil bmcClient from GetBMCClientFromBMC into resetBMC. Before calling
resetBMC, obtain a separate BMC client without the connectivity-check gate (or
use the dedicated reset connection) and pass that client so resetBMC can reach
Redfish ResetManager; preserve the existing reset logging and requeue behavior
on success.

---

Nitpick comments:
In `@internal/controller/bmc_controller.go`:
- Around line 474-494: Refactor the reset handling around bmcClient.ResetManager
to invert the err == nil check: return immediately on success, then process
non-nil errors without an else branch. Preserve the existing permanent-failure
handling, clearResetCondition calls, and retryable 5xx fallthrough to the final
log and nil return. Run make lint-fix and make test after editing.
- Around line 395-411: Update updateReadyConditionOnBMCFailure to return nil
after successfully patching the BMC Ready condition, rather than propagating the
original err; preserve returning the patch error so reconcile can retain the
configured ClientRetryInterval for BMC connection failures.

In `@internal/controller/suite_test.go`:
- Around line 180-196: Set a short non-zero FailureResetDelay in the
BMCReconciler initialization used by the suite setup, alongside ResetWaitTime
and ClientRetryInterval, so shouldResetBMC exercises the automatic-reset branch
while preserving the existing test timing behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 77b267d4-0ce3-4e2d-9121-8ed0c806ecbe

📥 Commits

Reviewing files that changed from the base of the PR and between 58397ba and 7ad92b4.

⛔ Files ignored due to path filters (2)
  • dist/chart/templates/crd/bmcs.metal.ironcore.dev.yaml is excluded by !**/dist/**
  • dist/install.yaml is excluded by !**/dist/**
📒 Files selected for processing (10)
  • api/v1alpha1/applyconfiguration/api/v1alpha1/bmcstatus.go
  • api/v1alpha1/applyconfiguration/internal/internal.go
  • api/v1alpha1/bmc_types.go
  • cmd/main.go
  • config/crd/bases/metal.ironcore.dev_bmcs.yaml
  • docs/api-reference/api.md
  • internal/controller/bmc_controller.go
  • internal/controller/bmc_controller_test.go
  • internal/controller/conditions.go
  • internal/controller/suite_test.go
💤 Files with no reviewable changes (1)
  • api/v1alpha1/applyconfiguration/internal/internal.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • api/v1alpha1/applyconfiguration/api/v1alpha1/bmcstatus.go
  • api/v1alpha1/bmc_types.go
  • docs/api-reference/api.md
  • config/crd/bases/metal.ironcore.dev_bmcs.yaml

Comment thread cmd/main.go Outdated
Comment thread internal/controller/bmc_controller.go Outdated
@afritzler
afritzler force-pushed the enh/bmc-pending-state branch 2 times, most recently from b34427f to da69358 Compare July 28, 2026 13:59
@afritzler

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

log.V(1).Info("Skipped BMC reconciliation while waiting for BMC reset to complete")
if err := r.patchBMCStatePending(ctx, bmcObj); err != nil {
return ctrl.Result{RequeueAfter: r.ClientRetryInterval}, nil
case resetWaitExpired:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe resetWaitExpired should use a dedicated ReasonResetExpired/ReasonResetTimeout reason, not ReasonResetFailed, to correctly distinguish a timeout from an actual failure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair point. Address this with 58e6b92

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/bmc_controller.go (1)

167-176: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve timeout and failure reasons when reconnecting.

This checks only whether ConditionReset exists, not whether it is True. After the timeout or failure paths set the condition to False, the next successful connectivity check rewrites its reason to ReasonResetComplete, falsely reporting a successful reset and erasing the new timeout/failure distinction. Only mark the reset complete when the existing condition has Status == metav1.ConditionTrue; also propagate lookup errors instead of ignoring them.

Proposed fix
-	if found, _ := r.Conditions.FindSlice(bmcObj.Status.Conditions, ConditionReset, &metav1.Condition{}); found {
+	resetCondition := &metav1.Condition{}
+	found, err := r.Conditions.FindSlice(bmcObj.Status.Conditions, ConditionReset, resetCondition)
+	if err != nil {
+		return ctrl.Result{}, fmt.Errorf("failed to find BMC reset condition: %w", err)
+	}
+	if found && resetCondition.Status == metav1.ConditionTrue {
🤖 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/controller/bmc_controller.go` around lines 167 - 176, Update the
reset-condition handling around Conditions.FindSlice and ConditionReset so
completion is recorded only when the existing condition has Status ==
metav1.ConditionTrue. Capture and propagate any lookup error instead of
discarding it, and leave False conditions from timeout or failure paths
unchanged so their reason and message are preserved.
🤖 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.

Outside diff comments:
In `@internal/controller/bmc_controller.go`:
- Around line 167-176: Update the reset-condition handling around
Conditions.FindSlice and ConditionReset so completion is recorded only when the
existing condition has Status == metav1.ConditionTrue. Capture and propagate any
lookup error instead of discarding it, and leave False conditions from timeout
or failure paths unchanged so their reason and message are preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ca9d6603-1e1e-4935-92f2-cc9f7c2e4687

📥 Commits

Reviewing files that changed from the base of the PR and between da69358 and 58e6b92.

📒 Files selected for processing (2)
  • internal/controller/bmc_controller.go
  • internal/controller/conditions.go

@afritzler
afritzler force-pushed the enh/bmc-pending-state branch 2 times, most recently from 3eccc83 to 9961a97 Compare August 5, 2026 13:54
@afritzler

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Signed-off-by: Andreas Fritzler <andreas.fritzler@sap.com>
Signed-off-by: Andreas Fritzler <andreas.fritzler@sap.com>
@afritzler
afritzler force-pushed the enh/bmc-pending-state branch from 9961a97 to 7a9b728 Compare August 5, 2026 15:08
@afritzler

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/bmc_controller.go (1)

137-149: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Auto-reset cannot issue a BMC reset on the failed-connection path.

Line 140 runs only after GetBMCClientFromBMC fails and returns no client. resetBMC receives that nil client, clears ConditionReset through failReset, and returns an error without calling ResetManager. The auto-reset path therefore cannot trigger a Redfish reset as configured; either prevent auto-reset until a BMC client is available, or issue the reset through a separate client path that can work without the same connectivity gate.

🤖 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/controller/bmc_controller.go` around lines 137 - 149, Update the
auto-reset branch in the BMC reconciliation error path around shouldResetBMC and
resetBMC so it does not pass the failed GetBMCClientFromBMC result when
bmcClient is unavailable. Only initiate resetBMC after obtaining a usable
client, or route the reset through a separate client-acquisition path that can
perform ResetManager without the failed-connection client; preserve the existing
reconnect and readiness-condition behavior when no reset-capable client is
available.
🧹 Nitpick comments (1)
internal/controller/bmc_controller.go (1)

167-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Propagate the FindSlice error instead of discarding it.

Line 167 discards the error and treats a decode failure as "condition absent". The reset completion is then skipped without any signal. Lines 431 and 496 handle the same call's error explicitly. Keep the handling uniform.

♻️ Proposed change
-	if found, _ := r.Conditions.FindSlice(bmcObj.Status.Conditions, ConditionReset, &metav1.Condition{}); found {
+	found, err := r.Conditions.FindSlice(bmcObj.Status.Conditions, ConditionReset, &metav1.Condition{})
+	if err != nil {
+		return ctrl.Result{}, fmt.Errorf("failed to find condition %s: %w", ConditionReset, err)
+	}
+	if found {

Note that err is already declared at Line 136, so this uses assignment rather than a new declaration.

🤖 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/controller/bmc_controller.go` around lines 167 - 177, Update the
reset-condition lookup in the BMC reconciliation flow to capture and propagate
the existing FindSlice error instead of discarding it. Reuse the err variable
already declared in the surrounding scope, return a contextual error on lookup
failure, and preserve the current condition-update behavior when the lookup
succeeds.
🤖 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/controller/bmc_controller.go`:
- Around line 145-148: Update the error return in the Reconcile flow around
updateReadyConditionOnBMCFailure to stop setting RequeueAfter when returning the
connection error; return the zero-value ctrl.Result with the error so
controller-runtime applies its backoff. Preserve ReconnectInterval for
successful or non-error requeue paths.
- Around line 402-418: Update updateReadyConditionOnBMCFailure to classify
non-HTTP transport errors, including connection timeouts and refusals from
gofish.ConnectContext, as ReasonConnectionFailed before patchCondition is
called. Preserve the existing HTTP status mappings and unknown-error fallback
for other errors.

---

Outside diff comments:
In `@internal/controller/bmc_controller.go`:
- Around line 137-149: Update the auto-reset branch in the BMC reconciliation
error path around shouldResetBMC and resetBMC so it does not pass the failed
GetBMCClientFromBMC result when bmcClient is unavailable. Only initiate resetBMC
after obtaining a usable client, or route the reset through a separate
client-acquisition path that can perform ResetManager without the
failed-connection client; preserve the existing reconnect and
readiness-condition behavior when no reset-capable client is available.

---

Nitpick comments:
In `@internal/controller/bmc_controller.go`:
- Around line 167-177: Update the reset-condition lookup in the BMC
reconciliation flow to capture and propagate the existing FindSlice error
instead of discarding it. Reuse the err variable already declared in the
surrounding scope, return a contextual error on lookup failure, and preserve the
current condition-update behavior when the lookup succeeds.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 90fb883d-614d-48ea-b036-13dde8714160

📥 Commits

Reviewing files that changed from the base of the PR and between 1403241 and 7a9b728.

⛔ Files ignored due to path filters (2)
  • dist/chart/templates/crd/bmcs.metal.ironcore.dev.yaml is excluded by !**/dist/**
  • dist/install.yaml is excluded by !**/dist/**
📒 Files selected for processing (10)
  • api/v1alpha1/applyconfiguration/api/v1alpha1/bmcstatus.go
  • api/v1alpha1/applyconfiguration/internal/internal.go
  • api/v1alpha1/bmc_types.go
  • cmd/main.go
  • config/crd/bases/metal.ironcore.dev_bmcs.yaml
  • docs/api-reference/api.md
  • internal/controller/bmc_controller.go
  • internal/controller/bmc_controller_test.go
  • internal/controller/conditions.go
  • internal/controller/suite_test.go
💤 Files with no reviewable changes (1)
  • api/v1alpha1/applyconfiguration/internal/internal.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • internal/controller/bmc_controller_test.go
  • api/v1alpha1/applyconfiguration/api/v1alpha1/bmcstatus.go
  • docs/api-reference/api.md
  • internal/controller/suite_test.go
  • api/v1alpha1/bmc_types.go
  • internal/controller/conditions.go
  • config/crd/bases/metal.ironcore.dev_bmcs.yaml
  • cmd/main.go

Comment on lines +145 to +148
RequeueAfter: r.ReconnectInterval,
}, nil
}
return ctrl.Result{RequeueAfter: r.BMCClientRetryInterval}, r.updateReadyConditionOnBMCFailure(ctx, bmcObj, err)
return ctrl.Result{RequeueAfter: r.ReconnectInterval}, r.updateReadyConditionOnBMCFailure(ctx, bmcObj, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does controller-runtime ignore Result.RequeueAfter when Reconcile returns a non-nil error?

💡 Result:

Yes, controller-runtime ignores the Result (including RequeueAfter) when the Reconcile function returns a non-nil error [1][2]. When a non-nil error is returned, the controller-runtime framework will automatically requeue the request using the controller's configured exponential backoff rate limiter [1][3][2]. The only exception to this behavior is if the returned error is a TerminalError, in which case the request is not requeued [1][2]. Because the Result is discarded whenever an error occurs, returning a non-zero Result (such as setting a specific RequeueAfter duration) alongside an error is ineffective and can be misleading [4][5][6]. The framework may also log a warning if both a non-nil error and a non-zero Result are returned simultaneously [4][5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate controller file =="
fd -a 'bmc_controller\.go$' . | sed 's#^\./##'

echo "== relevant outline =="
ast-grep outline internal/controller/bmc_controller.go --view expanded 2>/dev/null | sed -n '1,220p' || true

echo "== relevant lines =="
nl -ba internal/controller/bmc_controller.go | sed -n '1,220p'

echo "== search condition helper definitions/usages =="
rg -n "updateReadyConditionOnBMCFailure|ReconnectInterval|RequeueAfter|Reset" internal/controller/bmc_controller.go internal/controller -g '*.go'

Repository: ironcore-dev/metal-operator

Length of output: 3664


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate controller file =="
fd -a 'bmc_controller\.go$' . | sed 's#^\./##'

echo "== relevant lines =="
nl -ba internal/controller/bmc_controller.go | sed -n '120,160p'

echo "== helper definition search and contents =="
rg -n "func .*updateReadyConditionOnBMCFailure|updateReadyConditionOnBMCFailure|ReconnectInterval|RequeueAfter|Revert|Reset" internal/controller -g '*.go'

echo "== structural check for methods/functions in bmc_controller.go with concise Python parse =="
python3 - <<'PY'
from pathlib import Path
import re
p = Path('internal/controller/bmc_controller.go')
s = p.read_text()
for m in re.finditer(r'\b(func|const|var)\b', s):
    start = m.start()
    line = s.count('\n', 0, start) + 1
    snippet = s[start:start+80].replace('\n', ' ')
    if re.search(r'(updateReadyConditionOnBMCFailure|ReconnectInterval|RequeueAfter|Result\{|Reconcile\b)', snippet):
        print(f"{p}:{line}: {snippet[:90]}...")
PY

Repository: ironcore-dev/metal-operator

Length of output: 312


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant controller lines =="
awk 'NR>=125 && NR<=160 {printf "%5d\t%s\n", NR, $0}' internal/controller/bmc_controller.go

echo "== condition helper implementation =="
awk 'NR>=402 && NR<=420 {printf "%5d\t%s\n", NR, $0}' internal/controller/bmc_controller.go

echo "== retry/error return sites and helper names =="
python3 - <<'PY'
from pathlib import Path
p = Path('internal/controller/bmc_controller.go')
for line in [136,141,148]:
    txt = p.read_text().splitlines()
    if line <= len(txt):
        print(f"{line}: {txt[line-1]}")
PY
sed -n '136,152p;402,420p' internal/controller/bmc_controller.go

Repository: ironcore-dev/metal-operator

Length of output: 5062


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== find go.mod/controller-runtime version references =="
sed -n '1,120p' go.mod 2>/dev/null || true
rg -n "sigs\.k8s\.io/controller-runtime|controller-runtime|RequeueAfter|Result" go.mod go.sum pkg reconcile 2>/dev/null | head -80 || true

echo "== check local controller-runtime reconcile file if vendored/module cache present =="
for d in vendor/sigs.k8s.io/controller-runtime/pkg/reconcile reconcile; do
  [ -f "$d/reconcile.go" ] && { echo "--- $d/reconcile.go ---"; sed -n '1,240p' "$d/reconcile.go"; }
done

Repository: ironcore-dev/metal-operator

Length of output: 6253


Replace ReconnectInterval with controller-runtime backoff.

controller-runtime ignores Result.RequeueAfter when Reconcile returns an error. Line 148 returns r.ReconnectInterval plus the connection error from updateReadyConditionOnBMCFailure, so retries use the reconciler backoff instead of the intended interval.

🤖 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/controller/bmc_controller.go` around lines 145 - 148, Update the
error return in the Reconcile flow around updateReadyConditionOnBMCFailure to
stop setting RequeueAfter when returning the connection error; return the
zero-value ctrl.Result with the error so controller-runtime applies its backoff.
Preserve ReconnectInterval for successful or non-error requeue paths.

Comment on lines 402 to 418
func (r *BMCReconciler) updateReadyConditionOnBMCFailure(ctx context.Context, bmcObj *metalv1alpha1.BMC, err error) error {
httpErr := &schemas.Error{}
if errors.As(err, &httpErr) {
// only handle 5xx errors
reason, message := ReasonUnknownError, fmt.Sprintf("BMC connection error: %v", err)
if httpErr, ok := errors.AsType[*schemas.Error](err); ok {
switch httpErr.HTTPReturnedStatusCode {
case 401:
// Unauthorized error, likely due to bad credentials
if err := r.updateConditions(ctx, bmcObj, true, ConditionReady, corev1.ConditionFalse, ReasonAuthenticationFailed, "BMC credentials are invalid"); err != nil {
return fmt.Errorf("failed to set BMC unauthorized condition: %w", err)
}

reason, message = ReasonAuthenticationFailed, "BMC credentials are invalid"
case 500:
// Internal Server Error, might be transient
if err := r.updateConditions(ctx, bmcObj, true, ConditionReady, corev1.ConditionFalse, ReasonInternalError, "BMC internal server error"); err != nil {
return fmt.Errorf("failed to set BMC internal server error condition: %w", err)
}
reason, message = ReasonInternalError, "BMC internal server error"
case 503:
// Service Unavailable, might be transient
if err := r.updateConditions(ctx, bmcObj, true, ConditionReady, corev1.ConditionFalse, ReasonConnectionFailed, "BMC service unavailable"); err != nil {
return fmt.Errorf("failed to set BMC service unavailable condition: %w", err)
}
default:
if err := r.updateConditions(ctx, bmcObj, true, ConditionReady, corev1.ConditionFalse, ReasonUnknownError, fmt.Sprintf("BMC connection error: %v", err)); err != nil {
return fmt.Errorf("failed to set BMC error condition: %w", err)
}
}
} else {
if err := r.updateConditions(ctx, bmcObj, true, ConditionReady, corev1.ConditionFalse, ReasonUnknownError, fmt.Sprintf("BMC connection error: %v", err)); err != nil {
return fmt.Errorf("failed to set BMC error condition: %w", err)
reason, message = ReasonConnectionFailed, "BMC service unavailable"
}
}
if err := r.patchCondition(ctx, bmcObj, ConditionReady, corev1.ConditionFalse, reason, message); err != nil {
return fmt.Errorf("failed to set BMC ready condition: %w", err)
}
return err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how connection-level errors surface from the BMC client and which reasons are reachable.
set -euo pipefail

echo "=== Reason constants ==="
fd -t f 'conditions.go' -p 'internal/controller' | xargs rg -n 'Reason[A-Za-z]+\s*='

echo "=== Producers of schemas.Error vs raw transport errors ==="
rg -n -C4 'HTTPReturnedStatusCode' --glob '*.go'

echo "=== Client creation error paths ==="
fd -t f 'bmcutils.go' | xargs rg -n -C4 'func CreateBMCClient'

Repository: ironcore-dev/metal-operator

Length of output: 1609


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== relevant file list ==="
git ls-files | rg '(^|/)internal/controller/(bmc_controller|.*conditions).*\.go$|(^|/)schemas/.*\.go$|bmcutils\.go$'

echo "=== bmc_controller outline section ==="
if [ -f internal/controller/bmc_controller.go ]; then
  wc -l internal/controller/bmc_controller.go
  sed -n '120,170p;400,490p' internal/controller/bmc_controller.go
fi

echo "=== conditions file section ==="
conditions="$(git ls-files | rg '(^|/)conditions\.go$' | head -n 1)"
if [ -n "${conditions:-}" ]; then
  sed -n '60,110p' "$conditions"
fi

echo "=== schema error type references ==="
rg -n -C3 'type Error|HTTPReturnedStatusCode|Reason[A-Za-z]+' --glob '*.go' | sed -n '1,220p'

Repository: ironcore-dev/metal-operator

Length of output: 8577


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== bmcutils.go header and GetBMCClientFromBMC ==="
wc -l pkg/bmcutils/bmcutils.go
rg -n -C8 'func GetBMCClientFromBMC|BMCConnectivityCheckOption|New.*BMC|HTTPReturnedStatusCode|schemas\.Error' pkg/bmcutils/bmcutils.go

echo "=== package declarations of schemas files ==="
git ls-files | rg '/schemas/' | xargs -r sh -c 'echo "--- $1"; sed -n "1,40p" "$1"' sh {}

Repository: ironcore-dev/metal-operator

Length of output: 2895


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== bmcutils error constructors ==="
sed -n '1,80p;185,235p' pkg/bmcutils/bmcutils.go

echo "=== bmc package files ==="
git ls-files | rg '(^|/)bmc/'
for f in $(git ls-files | rg '(^|/)bmc/.*\.go$' | head -n 20); do
  echo "--- $f"
  wc -l "$f"
  rg -n -C4 'NewRedfishBMCClient|NewRedfishLocalBMCClient|HTTPReturnedStatusCode|schemas\.Error|type Error|connection|timeout|dial|http' "$f" | sed -n '1,160p'
done

echo "=== schemas package files ==="
git ls-files 'internal/**/*schema*' 'internal/**/schemas*' 'pkg/**/*schema*' 'pkg/**/schemas*'

Repository: ironcore-dev/metal-operator

Length of output: 24281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== bmc/bmc.go imports and error/HTTP types ==="
sed -n '1,80p' bmc/bmc.go
rg -n -C4 'type .*Error|HTTPReturnedStatusCode|func .*Error|HTTPStatus|StatusCode|Wrap|Errorf|errors\.' bmc/bmc.go

echo "=== redfish implementation error wrapping ==="
rg -n -C5 'NewRedfishBMCClient|Do\(|Client\.Do|GET|POST|HTTPReturnedStatusCode|SchemaErrors|errors\.Wrap|wrap|err :=' bmc/redfish.go bmc/redfish_local.go | sed -n '1,260p'

echo "=== tests related to errors and reset reasons ==="
rg -n -C3 'ReasonUnknownError|ReasonConnectionFailed|ReasonInternalError|shouldResetBMC|FailureResetDelay|HTTPReturnedStatusCode|connection refused|timeout|503|500|401' internal/controller/bmc_controller_test.go pkg/bmcutils/bmcutils_test.go bmc/*_test.go internal/controller/bmc_controller.go | sed -n '1,260p'

Repository: ironcore-dev/metal-operator

Length of output: 24541


🌐 Web query:

github.com/stmcginnis/gofish schemas Error HTTPReturnedStatusCode

💡 Result:

The HTTPReturnedStatusCode is a field within the common.Error struct in the stmcginnis/gofish Go library, which is used to capture error responses when interacting with Redfish or Swordfish services [1][2]. When the gofish client performs an HTTP request, it checks the response status code [3]. If the status code is not one of the success codes (typically 200, 201, 202, or 204), the library reads the response body and uses the schemas.ConstructError function to return a structured error [3]. This error object contains the HTTPReturnedStatusCode field, which holds the actual HTTP status code returned by the server [1][2]. Developers using the library often type-assert the error returned by client calls to the *common.Error type to inspect this field [4][5]. This is commonly used to handle specific conditions, such as ignoring or logging specific errors like 404 Not Found when polling for optional metrics [4][5][6]. Example of checking the status code: if err!= nil { if commonErr, ok:= err.(*common.Error); ok { if commonErr.HTTPReturnedStatusCode == http.StatusNotFound { // Handle 404 specifically } } } [4][5] Note that if the server returns a non-success status code but does not provide a standard Redfish error body, the library will still capture the status code in this field, allowing for generic error handling [2][7].

Citations:


Map transport-level BMC failures to ReasonConnectionFailed.

updateReadyConditionOnBMCFailure maps only HTTP 401, 500, and 503. A connection timeout or connection refused error from gofish.ConnectContext does not produce *schemas.Error, so it remains ReasonUnknownError.

shouldResetBMC only resets on ReasonInternalError or ReasonConnectionFailed. Network-level BMC failures therefore never trigger automatic reset. Treat transport errors as connection failures before calling this helper.

🤖 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/controller/bmc_controller.go` around lines 402 - 418, Update
updateReadyConditionOnBMCFailure to classify non-HTTP transport errors,
including connection timeouts and refusals from gofish.ConnectContext, as
ReasonConnectionFailed before patchCondition is called. Preserve the existing
HTTP status mappings and unknown-error fallback for other errors.

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

Labels

api-change breaking documentation Improvements or additions to documentation enhancement New feature or request

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants