Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

OSAC-2398: handle permanent K8s errors in reconcilers and guard no-op updates - #973

Closed
slintes wants to merge 5 commits into
osac-project:mainfrom
slintes:OSAC-2398
Closed

slintes wants to merge 5 commits into
osac-project:mainfrom
slintes:OSAC-2398

Conversation

@slintes

@slintes slintes commented Jul 27, 2026 •

Copy link
Copy Markdown

Summary

  • Detect permanent Kubernetes CRD validation errors (IsInvalid) on Create/Patch across all hub-backed reconcilers, marking resources as FAILED with a ValidationFailed condition instead of retrying indefinitely
  • Guard no-op updates in compute instance reconciler — skip Update() call when updateMask has no paths (implements the existing "only send an update if there are actual changes" comment)
  • Log swallowed updateErr in compute instance reconciler — when both reconcileErr and updateErr are non-nil, log the dropped updateErr at warn level so operators see when status persistence fails

Context

Replaces #957, #917, and #856. Original work by @ygalblum, rebased onto current main and extended with improvements from unresolved CodeRabbit feedback on #903.

Jira

OSAC-2398

Test plan

  • Unit tests pass (ginkgo run -r internal/controllers — all 18 suites pass)
  • gofmt and go build clean
  • Existing IsInvalid tests from original PR cover all reconcilers
  • Compute instance tests cover the no-op update guard and swallowed error logging

Assisted-by: Claude Code noreply@anthropic.com

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Kubernetes validation (“Invalid”) errors are now treated as permanent failures across provisioning flows.
    • Affected resources are marked FAILED with validation error details, instead of being retried.
    • ComputeInstance reconciliation now retries transient Kubernetes errors without changing state.
    • ComputeInstance now avoids unnecessary status update calls when nothing changed.
  • Tests
    • Added coverage for handling validation errors, transient retry behavior, and recovery after correcting an invalid spec.

ygalblum and others added 2 commits July 27, 2026 21:01
…rnetes CRD validation fails

Reconcilers now transition a resource to FAILED, with a clear condition message, when the Kubernetes API rejects a Create/Patch as invalid, instead of retrying forever.

When the generated Kubernetes CRD spec fails admission validation (e.g. an out-of-range field), the error is permanent — retrying the reconcile will never succeed. Resources were left stuck in STARTING/PROGRESSING indefinitely with no useful signal to the user about what went wrong.

- Detect Kubernetes "Invalid" API errors on Create/Patch across all hub-backed reconcilers (compute instances, clusters, bare metal instances, networking resources) and mark the resource FAILED with a ValidationFailed condition carrying the API error message
- For compute instances specifically, distinguish permanent validation errors from transient Kubernetes API errors (e.g. connection issues) so transient failures keep retrying without prematurely marking the resource as failed

Added unit tests covering: CRD validation failures on Create and on Patch resulting in a FAILED state with the expected condition, and transient (non-validation) Kubernetes errors continuing to be retried without changing state.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
Address unresolved CodeRabbit feedback from PR osac-project#903:

- Skip the Update() call when updateMask has no paths, matching
  the existing "only send an update if there are actual changes"
  comment that previously had no guard
- Log the swallowed updateErr at warn level when both reconcileErr
  and updateErr are non-nil, so operators see when status persistence
  fails after a reconciliation error

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Marc Sluiter <msluiter@redhat.com>
@openshift-ci-robot

openshift-ci-robot commented Jul 27, 2026 •

Copy link
Copy Markdown

@slintes: This pull request references OSAC-2398 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Detect permanent Kubernetes CRD validation errors (IsInvalid) on Create/Patch across all hub-backed reconcilers, marking resources as FAILED with a ValidationFailed condition instead of retrying indefinitely
  • Guard no-op updates in compute instance reconciler — skip Update() call when updateMask has no paths (implements the existing "only send an update if there are actual changes" comment)
  • Log swallowed updateErr in compute instance reconciler — when both reconcileErr and updateErr are non-nil, log the dropped updateErr at warn level so operators see when status persistence fails

Context

Replaces #957, #917, and #856. Original work by @ygalblum, rebased onto current main and extended with improvements from unresolved CodeRabbit feedback on #903.

Jira

OSAC-2398

Test plan

  • Unit tests pass (ginkgo run -r internal/controllers — all 18 suites pass)
  • gofmt and go build clean
  • Existing IsInvalid tests from original PR cover all reconcilers
  • Compute instance tests cover the no-op update guard and swallowed error logging

Assisted-by: Claude Code noreply@anthropic.com

🤖 Generated with Claude Code

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: slintes
Once this PR has been reviewed and has the lgtm label, please assign omer-vishlitzky for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7814b975-2d2c-496b-9a95-0ae734213aa8

📥 Commits

Reviewing files that changed from the base of the PR and between c725398 and f09fa3b.

📒 Files selected for processing (1)
  • internal/controllers/computeinstance/computeinstance_reconciler_function_test.go

Walkthrough

Kubernetes Invalid errors now transition multiple reconciled resources to FAILED with validation messages instead of being retried. ComputeInstance additionally distinguishes transient errors, skips empty updates, and tests failure and recovery paths.

Changes

Validation failure handling

Layer / File(s) Summary
Resource reconciler failure transitions
internal/controllers/{baremetalinstance,cluster,externalip,externalipattachment,externalippool,natgateway,securitygroup,subnet,virtualnetwork}/*_reconciler_function.go
Create and patch operations classify Kubernetes Invalid errors, set resources to FAILED, record error messages, and return without propagating terminal errors.
ComputeInstance retry and persistence flow
internal/controllers/computeinstance/computeinstance_reconciler_function.go
Transient errors remain retryable, Invalid errors set FAILED status, empty update masks skip API updates, and update failures log both errors.
ComputeInstance error-path coverage
internal/controllers/computeinstance/computeinstance_reconciler_function_test.go
Tests cover Invalid create and patch errors, validation conditions, transient errors, and recovery after specification correction.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: approved, lgtm

Suggested reviewers: tzvatot, rgolangh, jhernand

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: permanent K8s error handling in reconcilers and skipping no-op updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets or credential-shaped literals were added; the new strings are validation/error messages and test fixtures only.
No-Weak-Crypto ✅ Passed Scanned all 11 modified files; no weak-crypto APIs, custom crypto, or secret/token comparisons were introduced.
No-Injection-Vectors ✅ Passed Scanned all touched files; only K8s Create/Patch/Update, status setters, and logging were added. No eval/exec, yaml.load, os.system, or SQL injection sinks found.
Container-Privileges ✅ Passed No changed manifests introduce privileged settings; repo search found no privileged:true, host* flags, allowPrivilegeEscalation:true, or SYS_ADMIN in manifest-like files.
No-Sensitive-Data-In-Logs ✅ Passed New logs only emit error strings and resource names/namespaces; no credentials, PII, or customer data are logged in the modified files.
Ai-Attribution ✅ Passed AI usage is attributed with Assisted-by trailers in the PR and commits; I found no Co-Authored-By trailers for AI tools.
✨ 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

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/controllers/cluster/cluster_reconciler_function.go`:
- Around line 226-229: Update the Invalid-error handling in the reconciler’s
validation and Patch paths to avoid calling setFailed(err), which permanently
suppresses future reconciliation. Record the failed spec identity/generation
using the existing suppression mechanism, and skip retries only when the current
invalid payload exactly matches that recorded failure; allow reconciliation
after the user changes the spec.
- Around line 478-493: Update setFailed to call updateCondition with
CLUSTER_CONDITION_TYPE_FAILED instead of CLUSTER_CONDITION_TYPE_PROGRESSING,
while preserving the FAILED state transition and existing status, reason, and
error message.

In
`@internal/controllers/computeinstance/computeinstance_reconciler_function_test.go`:
- Around line 1996-2336: Add a recovery-path spec in the “Kubernetes validation
error handling” Describe block using the existing two-reconcile pattern from
“should create CR on second reconcile after hub is persisted.” Run f.run first
with an invalid instance type and verify FAILED, then update the same
computeInstance to a valid specification and reconcile again against a client
that succeeds. Assert the instance leaves FAILED and reaches the expected
successful state, preserving the existing Invalid-error coverage.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 750f6268-a6e7-4037-a2c5-70d42491a513

📥 Commits

Reviewing files that changed from the base of the PR and between 73ae26e and aa2eae5.

📒 Files selected for processing (11)
  • internal/controllers/baremetalinstance/baremetalinstance_reconciler_function.go
  • internal/controllers/cluster/cluster_reconciler_function.go
  • internal/controllers/computeinstance/computeinstance_reconciler_function.go
  • internal/controllers/computeinstance/computeinstance_reconciler_function_test.go
  • internal/controllers/externalip/external_ip_reconciler_function.go
  • internal/controllers/externalipattachment/external_ip_attachment_reconciler_function.go
  • internal/controllers/externalippool/external_ip_pool_reconciler_function.go
  • internal/controllers/natgateway/nat_gateway_reconciler_function.go
  • internal/controllers/securitygroup/securitygroup_reconciler_function.go
  • internal/controllers/subnet/subnet_reconciler_function.go
  • internal/controllers/virtualnetwork/virtual_network_reconciler_function.go

Comment on lines +226 to +229
if apierrors.IsInvalid(err) {
t.setFailed(err)
return nil
}

@coderabbitai coderabbitai Bot Jul 27, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'cluster_reconciler_function.go' . || true

echo "== file stats =="
file="$(fd 'cluster_reconciler_function.go' . | head -n 1)"
wc -l "$file"

echo "== relevant sections =="
sed -n '1,280p' "$file" | nl -ba
echo "== setFailed and state transitions =="
sed -n '440,510p' "$file" | nl -ba

echo "== occurrences of setFailed/state constants near file =="
rg -n "setFailed|CLUSTER_STATE_FAILED|PROGRESSING|READY|state\\s*!=" "$file"

Repository: osac-project/fulfillment-service

Length of output: 415


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="./internal/controllers/cluster/cluster_reconciler_function.go"

echo "== relevant sections =="
awk 'NR>=1 && NR<=280 {printf "%7d %s\n", NR, $0}' "$file"
echo "== setFailed and state transitions =="
awk 'NR>=440 && NR<=510 {printf "%7d %s\n", NR, $0}' "$file"
echo "== occurrences of setFailed/state constants near file =="
grep -nE "setFailed|CLUSTER_STATE_FAILED|PROGRESSING|READY|state !=|if state|SetClusterState|ClusterStatus|ValidationFailed|Failed" "$file" || true

Repository: osac-project/fulfillment-service

Length of output: 13966


Do not treat Invalid errors as terminal FAILED states.

setFailed() sets state to FAILED, but update() returns immediately for any state that is not PROGRESSING or READY before syncing the spec. If a Kubernetes validation failure sends the cluster into FAILED, future re-validations are skipped even after the user fixes the spec. Store the failed spec identity/generation for safe suppression instead, and only block retries that have exactly the same invalid payload. This also applies to the Patch path at lines 243-246.

🤖 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/controllers/cluster/cluster_reconciler_function.go` around lines 226
- 229, Update the Invalid-error handling in the reconciler’s validation and
Patch paths to avoid calling setFailed(err), which permanently suppresses future
reconciliation. Record the failed spec identity/generation using the existing
suppression mechanism, and skip retries only when the current invalid payload
exactly matches that recorded failure; allow reconciliation after the user
changes the spec.

Source: Learnings

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[AI-Assisted] Valid concern. The state guard at lines 167-171 is pre-existing code — our PR adds a new path INTO that terminal state via setFailed(), but the guard itself predates this change.

Fixing recovery properly requires either removing the state guard (which was intentional — comment on line 164 says "Do nothing if the cluster is in a terminal failure state") or adding spec-generation tracking to skip retries only for the same invalid payload. Both are design changes beyond this bugfix scope.

Note that only the cluster reconciler has this issue — all other reconcilers have no state guard and naturally recover when the spec is corrected (verified by new recovery-path test in c725398).

Our PR still improves cluster behavior: previously IsInvalid errors retried silently forever with no error message. Now the user at least sees FAILED state with ValidationFailed reason and the specific validation error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@slintes, agreed. The current change introduces a new terminal path into the pre-existing state guard, so recovery after a corrected invalid spec remains a valid—but separately scoped—design issue.

Keeping the user-visible FAILED status and ValidationFailed condition is still an improvement over silent retries. I’ll leave this finding unresolved for follow-up rather than blocking #973. Would you like me to open a GitHub issue for generation/fingerprint-based retry suppression in internal/controllers/cluster/cluster_reconciler_function.go?

🐇💭


✏️ Learnings added
Learnt from: slintes
Repo: osac-project/fulfillment-service PR: 973
File: internal/controllers/cluster/cluster_reconciler_function.go:226-229
Timestamp: 2026-07-28T11:45:01.601Z
Learning: In `internal/controllers/cluster/cluster_reconciler_function.go`, the intentional terminal-state guard skips reconciliation unless a Cluster is in PROGRESSING or READY. PR `#973` introduces FAILED state on Kubernetes Invalid errors, so corrected Cluster specs cannot currently recover; address this cluster-specific behavior in a follow-up through spec generation or payload-fingerprint-aware failure suppression rather than assuming it behaves like the other reconcilers.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ygalblum
Repo: osac-project/fulfillment-service PR: 957
File: internal/controllers/externalip/external_ip_reconciler_function.go:411-420
Timestamp: 2026-07-25T01:22:32.127Z
Learning: In Go hub-resource reconciler implementations, do not treat a resource status of FAILED after a Kubernetes Invalid error as a terminal state that suppresses future reconciliations. If the Kubernetes API rejected the resource due to an Invalid validation error, the resource spec may have changed and must be retried. Implement “safe suppression” only by comparing the currently observed failed spec identity (e.g., desired-spec generation, revision, or a computed fingerprint) against the identity of the version that failed validation; only suppress if they match exactly, otherwise allow reconciliation to proceed.

Comment thread internal/controllers/cluster/cluster_reconciler_function.go
…lure

Verify that compute instance reconciler retries and creates the K8s CR
after a previous IsInvalid failure, proving no state guard blocks
recovery when the spec is corrected.

Addresses CodeRabbit review feedback on PR osac-project#973.

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Marc Sluiter <msluiter@redhat.com>

@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: 2

🤖 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/controllers/computeinstance/computeinstance_reconciler_function_test.go`:
- Around line 2337-2432: Extract the repeated test fixture setup from the four
specs in this Describe into shared BeforeEach initialization and small helpers
such as newFakeClient(interceptor.Funcs) and newComputeInstance. Centralize
scheme registration, hubCache, hubsClient, computeInstancesClient, function
construction, and the common compute instance fields; leave each spec
responsible only for its interceptor behavior and instance-type core variation.
- Around line 2434-2451: The recovery assertions after the second f.run call are
incomplete. In the recovery test, assert that
computeInstance.GetStatus().GetState() is no longer
COMPUTE_INSTANCE_STATE_FAILED and that CONFIGURATION_APPLIED is no longer
ValidationFailed, while preserving the existing CR creation checks.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1f3cefe7-6569-4d59-bb32-9bfb041ed6de

📥 Commits

Reviewing files that changed from the base of the PR and between aa2eae5 and c725398.

📒 Files selected for processing (1)
  • internal/controllers/computeinstance/computeinstance_reconciler_function_test.go

Comment thread internal/controllers/computeinstance/computeinstance_reconciler_function_test.go Outdated
…specs

Assisted-by: Claude Code <noreply@anthropic.com>
Signed-off-by: Marc Sluiter <msluiter@redhat.com>
@slintes

slintes commented Aug 3, 2026

Copy link
Copy Markdown
Author

[AI-Assisted] This PR has been migrated to the mono repo: osac-project/osac#115. Closing in favor of that PR.

@slintes slintes closed this Aug 3, 2026

This branch was previously deployed

1 inactive deployment
e2e-test — f09fa3bf Deployed Jul 30, 2026 by masayag via e2e-vmaas-full-install / e2e #850
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants