Skip to content

CNTRLPLANE-3532: migrate CPO status patches to statuspatching helpers - #8966

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
vsolanki12:vsolanki/cntrlplane-3532-tier2-optimistic-lock
Aug 27, 2026
Merged

CNTRLPLANE-3532: migrate CPO status patches to statuspatching helpers#8966
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
vsolanki12:vsolanki/cntrlplane-3532-tier2-optimistic-lock

Conversation

@vsolanki12

@vsolanki12 vsolanki12 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Migrates 9 status patch call sites in the CPO to use statuspatching.PatchStatus / PatchStatusCondition, adding retry-on-conflict and consistent optimistic locking.

  • hostedcontrolplane_controller.go: 7 sites migrated (reconcileDeletion, update, reconcileValidIDPConfigurationCondition, removeCloudResources, reconcileDefaultSecurityGroup)
  • reencryption.go: 1 site migrated (HCCO re-encryption controller)
  • 2 batch-patch sites deferred — they accumulate status across the full reconcile loop and require restructuring

Part of the broader CNTRLPLANE-3532 migration. Depends on PR #8782 (merged).

This PR alone does not close CNTRLPLANE-3532. Remaining work tracked as follow-up PRs:

  • HCCO resources.go: destroyCloudResources's CloudResourcesDestroyed condition patch is still raw MergeFromWithOptimisticLock, not yet on the shared helper (the other two HCCO sites were already migrated via OCPBUGS-93462: Fix stale resourceVersion in HCCO patchHCPStatusCondition #8902).
  • Route status patch in removeHCPIngressFromRoutes (hostedcontrolplane_controller.go) — currently bare client.MergeFrom with no optimistic lock at all.
  • support/statuspatching JSON Patch (RFC 6902) variant + nullable-field test (Jira AC, not yet implemented).
  • Static analysis linter + make lint integration.
  • AGENTS.md guidance update.

Which issue(s) this PR fixes:

Part of CNTRLPLANE-3532 — does not fully close it, see remaining work above.

Special notes for your reviewer:

  • 2 batch-patch sites (lines 686, 869 in hostedcontrolplane_controller.go) are intentionally skipped — they accumulate status changes from the entire reconcile loop and migrating them requires restructuring the reconcile flow.
  • The originalHostedControlPlane parameter was removed from reconcileDeletion since the migrated helpers handle re-fetching internally.
  • Per @bryan-cox's review: reconcileValidIDPConfigurationCondition and reconcileDefaultSecurityGroup now guard against a stale hcp.Generation (spec changed mid-flight) before patching, rather than blindly re-applying a value computed from stale state on PatchStatus's internal retry. removeCloudResources's timeout branch re-checks CloudResourcesDestroyed against the freshly re-fetched object before overwriting it, since HCCO can concurrently set it to True. reencryption.go's workingCopy pattern was reviewed for the same class of bug — confirmed no live race (single-owner fields, controller-runtime serializes reconciles per key) and documented the invariant in a code comment; added the requested patch-error-path test.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of control plane status updates during AWS security-group operations, release-image checks, identity-provider validation, and cloud-resource cleanup.
    • AWS security-group identifiers are now retained after successful creation without overwriting existing platform status.
    • Improved persistence and accuracy of secret-encryption migration status, conditions, completion reporting, and migration metrics.
  • Tests

    • Added coverage for security-group failures, deletion status persistence, identity-provider readiness, and platform-status updates.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 9, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 9, 2026

Copy link
Copy Markdown

@vsolanki12: This pull request references CNTRLPLANE-3532 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 story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Migrates 9 status patch call sites in the CPO to use statuspatching.PatchStatus / PatchStatusCondition, adding retry-on-conflict and consistent optimistic locking.

  • hostedcontrolplane_controller.go: 7 sites migrated (reconcileDeletion, update, reconcileValidIDPConfigurationCondition, removeCloudResources, reconcileDefaultSecurityGroup)
  • reencryption.go: 1 site migrated (HCCO re-encryption controller)
  • 2 batch-patch sites deferred — they accumulate status across the full reconcile loop and require restructuring

Part of the broader CNTRLPLANE-3532 migration. Depends on PR #8782 (merged).

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3532

Special notes for your reviewer:

  • 2 batch-patch sites (lines 686, 869 in hostedcontrolplane_controller.go) are intentionally skipped — they accumulate status changes from the entire reconcile loop and migrating them requires restructuring the reconcile flow.
  • The originalHostedControlPlane parameter was removed from reconcileDeletion since the migrated helpers handle re-fetching internally.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

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 added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 9, 2026
@openshift-ci

openshift-ci Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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 controllers now use statuspatching for targeted HostedControlPlane status and condition updates. This covers deletion, validation, timeout, scaling, and AWS default security-group reconciliation. Successful security-group creation records the AWS security-group ID. Re-encryption reconciliation patches encryption status and conditions and updates migration metrics. Tests verify persisted status outcomes.

Suggested reviewers: devguyio, enxebre

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Structure And Quality ⚠️ Warning The added TestReconcileDefaultSecurityGroup contains message-less Gomega assertions, including g.Expect(err).ToNot(HaveOccurred()) and status reads at lines 4576-4602. Add diagnostic messages to every new assertion, especially client reads, error checks, and condition/status comparisons, so failures identify the operation and expected state.
✅ Passed checks (10 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.
Stable And Deterministic Test Names ✅ Passed The PR adds only fixed literal t.Run names; they contain no generated IDs, timestamps, namespaces, nodes, IPs, or UUIDs, and it adds no Ginkgo title calls.
Topology-Aware Scheduling Compatibility ✅ Passed The diff only changes status patching, encryption metrics, and tests. It adds no deployments, replicas, affinities, topology spreads, node selectors, tolerations, or PDBs.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The PR adds a standard Go unit test using testing.T, t.Run, a fake client, and mocked AWS calls; it adds no Ginkgo e2e test or external connectivity requirement.
No-Weak-Crypto ✅ Passed The PR changes status patching and condition synchronization only; added-line and changed-file scans show no MD5, SHA1, DES, RC4, Blowfish, ECB, custom crypto, or secret comparison.
Container-Privileges ✅ Passed The PR changes only three Go source/test files; the diff adds no privileged, hostPID, hostNetwork, hostIPC, SYS_ADMIN, root, or allowPrivilegeEscalation manifest settings.
No-Sensitive-Data-In-Logs ✅ Passed The PR diff adds no production logging calls or sensitive log fields. Existing encryption fingerprint logs are unchanged, and statuspatching performs no logging.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: migrating CPO status patch call sites to statuspatching helpers.
✨ 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.

@openshift-ci openshift-ci Bot added area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release and removed do-not-merge/needs-area labels Jul 9, 2026
@vsolanki12
vsolanki12 force-pushed the vsolanki/cntrlplane-3532-tier2-optimistic-lock branch from fb8a23c to d73cc0d Compare July 9, 2026 13:57
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.00000% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.08%. Comparing base (92e0058) to head (48712e0).
⚠️ Report is 91 commits behind head on main.

Files with missing lines Patch % Lines
...ostedcontrolplane/hostedcontrolplane_controller.go 68.91% 17 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8966      +/-   ##
==========================================
+ Coverage   45.99%   46.08%   +0.08%     
==========================================
  Files         781      784       +3     
  Lines       98072    98746     +674     
==========================================
+ Hits        45110    45508     +398     
- Misses      49892    50161     +269     
- Partials     3070     3077       +7     
Files with missing lines Coverage Δ
...goperator/controllers/reencryption/reencryption.go 73.30% <100.00%> (+3.95%) ⬆️
support/statuspatching/statuspatching.go 100.00% <100.00%> (ø)
...ostedcontrolplane/hostedcontrolplane_controller.go 51.65% <68.91%> (+3.13%) ⬆️

... and 20 files with indirect coverage changes

Flag Coverage Δ
cmd-support 39.19% <100.00%> (+0.03%) ⬆️
cpo-hostedcontrolplane 48.92% <68.91%> (+0.56%) ⬆️
cpo-other 46.16% <100.00%> (+0.14%) ⬆️
hypershift-operator 57.14% <ø> (-0.03%) ⬇️
other 34.71% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@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

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go`:
- Around line 1117-1139: The status patch in hostedcontrolplane_controller.go is
using a stale copy of HostedControlPlane and will overwrite earlier updates made
in update() and reconcileCPOV2. Fix the PatchStatus call to patch the current
in-memory hostedControlPlane state, or explicitly merge the existing status
fields back before setting ValidReleaseInfo. Keep the existing status mutations
such as Ready, KubeConfig, KubeadminPassword, ControlPlaneVersion, Initialized,
and prior conditions intact when applying the patch.

In
`@control-plane-operator/hostedclusterconfigoperator/controllers/reencryption/reencryption.go`:
- Around line 76-89: The `desiredCondition` in `reconcile` is a pointer into
`hcp.Status.Conditions`, so `statuspatching.PatchStatus` may re-fetch and
overwrite the backing slice before the callback uses it. Capture the condition
by value before calling `PatchStatus` (for example, copy the result of
`meta.FindStatusCondition` into a standalone variable) and then use that copied
value inside the patch callback when setting `hcp.Status.Conditions`.
🪄 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 YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: ddb9d783-3baf-45e9-a217-5ed2a8755bb9

📥 Commits

Reviewing files that changed from the base of the PR and between 1f2811f and fb8a23c.

📒 Files selected for processing (3)
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
  • control-plane-operator/hostedclusterconfigoperator/controllers/reencryption/reencryption.go

@vsolanki12
vsolanki12 force-pushed the vsolanki/cntrlplane-3532-tier2-optimistic-lock branch from d73cc0d to 7848ce8 Compare July 9, 2026 14:54
@hypershift-jira-solve-ci

hypershift-jira-solve-ci Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Both failures are now fully analyzed. Let me produce the final report.

Test Failure Analysis Complete

Job Information


  • Konflux Job: Red Hat Konflux / control-plane-operator-main-on-pull-request
  • Check Run ID: 86150298660
  • PipelineRun: control-plane-operator-main-on-pull-request-gkspc

Test Failure Analysis

Error

# Job 1: verify / Verify — make deps failure
golang.org/x/net@v0.53.0: read "https://proxy.golang.org/golang.org/x/net/@v/v0.53.0.zip": stream error: stream ID 773; INTERNAL_ERROR; received from peer
github.com/alecthomas/chroma/v2@v2.23.1: read "https://proxy.golang.org/github.com/alecthomas/chroma/v2/@v/v2.23.1.zip": stream error: stream ID 787; INTERNAL_ERROR; received from peer
make: *** [Makefile:552: deps] Error 1

# Job 2: Red Hat Konflux / control-plane-operator — rpms-signature-scan image pull failure
the step "rpms-signature-scan" in TaskRun failed to pull the image "quay.io/konflux-ci/tools@sha256:cdab2b0f6e01ed7852ad4c5376fd58bdbfe0db1379dd5acf42aad722a0f1e168":
Back-off pulling image: ErrImagePull: copying system image from manifest list: parsing image configuration: fetching blob: received unexpected HTTP status: 503 Service Unavailable.

Summary

Both failures are transient infrastructure flakes completely unrelated to the code changes in PR #8966. The verify / Verify job failed because the Go module proxy (proxy.golang.org) returned HTTP/2 INTERNAL_ERROR stream errors while downloading dependency zip files during make deps. The Konflux / control-plane-operator job failed because Quay.io returned 503 Service Unavailable when the rpms-signature-scan Tekton task attempted to pull its tooling image. All 16 other Konflux pipeline tasks (including build, security scans, and SBOM generation) succeeded. Retrying both jobs should resolve these failures.

Root Cause

Job 1 — verify / Verify:
The make generate update target invokes make deps, which builds tools from hack/tools. During go build -tags=tools, the Go module proxy at proxy.golang.org returned HTTP/2 stream-level INTERNAL_ERROR responses for two module zip downloads:

  1. golang.org/x/net@v0.53.0 — cascading across 7+ import chains (html/charset, proxy, http2, grpc, etc.)
  2. github.com/alecthomas/chroma/v2@v2.23.1 — via golangci-lint → goformat → chroma/quick

These are server-side errors from the Go module proxy CDN, not local network issues or code problems.

Job 2 — Red Hat Konflux / control-plane-operator-main-on-pull-request:
The pipeline completed 16 of 17 tasks successfully. The sole failing task was rpms-signature-scan, which has status TaskRunImagePullFailed. The Tekton pod could not pull quay.io/konflux-ci/tools@sha256:cdab2b... because Quay.io returned HTTP 503 during blob fetch. This also caused the downstream enterprise-contract check to be cancelled (it never ran since it depends on this pipeline).

Neither failure is related to the PR's code changes (migrating CPO status patches to statuspatching helpers).

Recommendations
  1. Rerun both jobs — these are transient infrastructure failures that will resolve on retry.
  2. For the Verify job: Use the GitHub Actions "Re-run failed jobs" button or push an empty commit to re-trigger.
  3. For the Konflux job: Re-trigger via the Konflux UI or add a /retest comment if supported. The downstream enterprise-contract check will automatically run once the pipeline succeeds.
  4. No code changes are needed in PR CNTRLPLANE-3532: migrate CPO status patches to statuspatching helpers #8966.
Evidence
Evidence Detail
Verify failed step Step 4 — "Run make generate update" at Makefile:552 (deps target)
Go proxy error 1 golang.org/x/net@v0.53.0 — HTTP/2 stream ID 773 INTERNAL_ERROR from proxy.golang.org
Go proxy error 2 github.com/alecthomas/chroma/v2@v2.23.1 — HTTP/2 stream ID 787 INTERNAL_ERROR from proxy.golang.org
Konflux failed task rpms-signature-scan — status TaskRunImagePullFailed (8s duration)
Konflux image pull error quay.io/konflux-ci/tools@sha256:cdab2b... — HTTP 503 Service Unavailable from Quay.io
Konflux passing tasks 16/17 tasks passed (init, clone, build-images, clair-scan, sast-snyk, etc.)
Konflux cancelled downstream enterprise-contract check cancelled due to pipeline failure
Other Konflux pipelines hypershift-operator-main-on-pull-request ✅ passed
PR HEAD SHA 7848ce80b2b3931be0f1cf2fecb7582cb036fc0c

@vsolanki12
vsolanki12 marked this pull request as ready for review July 13, 2026 09:27
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 13, 2026
@openshift-ci
openshift-ci Bot requested review from devguyio and enxebre July 13, 2026 09:28
@vsolanki12 vsolanki12 closed this Jul 13, 2026
@vsolanki12 vsolanki12 reopened this Jul 13, 2026

@cblecker cblecker left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The migration to statuspatching helpers looks well-executed across all call sites. F1 (metrics regression in reencryption.go) is the main concern — the rest are suggestions.

}

// Record metrics when encryption status changed.
if !equality.Semantic.DeepEqual(previousEncryption, desiredEncryption) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

recordMigrationState is now gated behind the DeepEqual check, but the old code called it unconditionally on every reconcile. After an HCCO pod restart in steady state (no encryption change), all hypershift_encryption_migration_state gauges stay at zero indefinitely — the "idle" gauge is never re-set to 1. This could confuse dashboards/alerts until the next key rotation, which may be weeks away.

Consider moving recordMigrationState outside the if !equality.Semantic.DeepEqual(...) block so it runs unconditionally, matching the old behavior. recordMigrationDuration should stay inside the guard since it should only fire on actual transitions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Moved recordMigrationState outside the DeepEqual guard so it runs unconditionally on every reconcile, matching the old behavior. recordMigrationDuration stays inside the guard since it should only fire on actual transitions.


AI-assisted response via Claude Code


func (r *HostedControlPlaneReconciler) reconcileDeletion(ctx context.Context, hostedControlPlane *hyperv1.HostedControlPlane, originalHostedControlPlane *hyperv1.HostedControlPlane) (ctrl.Result, error) {
func (r *HostedControlPlaneReconciler) reconcileDeletion(ctx context.Context, hostedControlPlane *hyperv1.HostedControlPlane) (ctrl.Result, error) {
condition := &metav1.Condition{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: condition is declared as a pointer (&metav1.Condition{...}) and then dereferenced (*condition) when passed to PatchStatusCondition. The other migrated sites (reconcileDefaultSecurityGroup, removeCloudResources) use value types or inline literals. Switching to a value type here would be more consistent and avoids the unnecessary indirection.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Changed condition from *metav1.Condition to metav1.Condition — consistent with the other migrated sites now.


AI-assisted response via Claude Code

if err := statuspatching.PatchStatus(ctx, r.Client, hcp, func() error {
meta.SetStatusCondition(&hcp.Status.Conditions, condition)
if creationErr == nil {
hcp.Status.Platform = &hyperv1.PlatformStatus{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pre-existing, but worth noting since the PatchStatus migration touches this: the callback replaces the entire hcp.Status.Platform struct with a new one containing only the security group ID. If PlatformStatus gains additional fields in the future, they'd be silently cleared on every reconcile. With PatchStatus retrying on conflict, the fresh struct also discards whatever the server has at retry time.

Consider initializing hcp.Status.Platform / hcp.Status.Platform.AWS if nil instead of replacing, then setting only DefaultWorkerSecurityGroupID.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Changed to init-if-nil pattern — hcp.Status.Platform and hcp.Status.Platform.AWS are now initialized only if nil, then only DefaultWorkerSecurityGroupID is set. This preserves any other fields that may be added to PlatformStatus in the future.


AI-assisted response via Claude Code

Status: metav1.ConditionTrue,
Reason: hyperv1.AsExpectedReason,
Message: hyperv1.AllIsWellMessage,
ObservedGeneration: hostedControlPlane.Generation,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not a regression (old code also re-fetched before referencing Generation), but ObservedGeneration inside the PatchStatus closure will reflect the re-fetched HCP's generation, which may be newer than the generation used to compute missingImages. If the spec changed between the original read and the re-fetch, the condition content won't match what that generation actually means. A follow-up reconcile self-corrects, so this is minor — just flagging in case you want to snapshot the generation before the PatchStatus call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. This is pre-existing — the old code also re-fetched before referencing Generation. A follow-up reconcile self-corrects, so leaving as-is for now.


AI-assisted response via Claude Code

@vsolanki12
vsolanki12 force-pushed the vsolanki/cntrlplane-3532-tier2-optimistic-lock branch from 7848ce8 to 4237039 Compare July 15, 2026 04:08
// Capture desired status changes computed by reconcile().
// Copy by value — PatchStatus re-fetches hcp, which replaces the backing slice.
desiredEncryption := *hcp.Status.SecretEncryption.DeepCopy()
var desiredCondition metav1.Condition

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Potential semantic narrowing: The original MergeFrom(originalHCP) patch captured all status mutations made by reconcile(). This new code snapshots only SecretEncryption and the EtcdDataEncryptionUpToDate condition, then replays just those two fields inside the PatchStatus closure.

If reconcile() (or any of its sub-functions like handleInitialBootstrap, startNewRotation, handleMigratingPhase, etc.) sets other status fields or conditions beyond these two, those changes are silently dropped after PatchStatus re-fetches the object.

Is EtcdDataEncryptionUpToDate the only condition reconcile() touches? If so this is fine — but worth a comment saying so. If not, the other conditions need to be captured and replayed too.

@vsolanki12 vsolanki12 Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Confirmed — reconcile() only mutates SecretEncryption and the EtcdDataEncryptionUpToDate condition. No other status fields or conditions. Added a comment on the snapshot block stating this explicitly.

@openshift-ci openshift-ci Bot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. area/api Indicates the PR includes changes for the API area/ci-tooling Indicates the PR includes changes for CI or tooling area/cli Indicates the PR includes changes for CLI area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release labels Jul 28, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@vismishr: This PR has been marked as verified by @vismishr.

Details

In response to this:

/verified by @vismishr

all results in https://vismishr.github.io/pr-8966-test-verification.html , tested on live cluster

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.

@devguyio

Copy link
Copy Markdown
Contributor

/uncc @devguyio

@openshift-ci
openshift-ci Bot removed the request for review from devguyio August 24, 2026 08:47

@bryan-cox bryan-cox left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for splitting this migration into smaller pieces. I see that #8782 and #8968 are related and that this PR explicitly defers two call sites.

I’m requesting changes because several migrated call sites compute their desired status before PatchStatus performs its internal fetch/retry. The optimistic lock therefore protects only the freshly fetched object, not the state used to calculate the update, allowing newer status or spec changes to be overwritten. The inline comments identify the concrete cases.

The PR also describes itself as fixing CNTRLPLANE-3532, but several Jira acceptance criteria remain unimplemented or untracked: the shared JSON Patch variant and nullable-field test, the status-patching linter, migration of remaining direct HCP patches, and AGENTS.md guidance. Please link concrete follow-up work and adjust the completion wording, or complete those criteria here.

Finally, TESTING.md requires direct tests for modified functions; TestUpdate, TestReconcileValidIDPConfigurationCondition, and a primary status-patch error case for re-encryption are missing. The commit subject also includes the Jira ID, contrary to DEVELOPMENT.md.

if err := r.Status().Patch(ctx, hcp, client.MergeFromWithOptions(originalHCP, client.MergeFromWithOptimisticLock{})); err != nil {
return fmt.Errorf("failed to patch valid IDP configuration condition: %w", err)
}
if err := statuspatching.PatchStatusCondition(ctx, r.Client, hcp, &hcp.Status.Conditions, new); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: new is calculated before PatchStatusCondition, but the helper re-fetches the HCP before applying it and on every retry. If the IDP configuration changes meanwhile, this applies a stale validation result to the latest object without producing a conflict. Please detect that the evaluated generation changed and rerun the full reconciliation. Also add TestReconcileValidIDPConfigurationCondition as required by TESTING.md.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Guarded against this: captured hcp.Generation before computing the condition, and inside PatchStatus's closure (using the freshly re-fetched hcp) return an error if generation changed instead of patching a stale evaluation — forces the whole reconcile to requeue and re-evaluate. Note: this catches spec changes but not IDP-override-annotation-only changes (annotations don't bump Generation); flagged that gap in a code comment since it's narrow (IBM Cloud-specific) and low severity. Added TestReconcileValidIDPConfigurationCondition covering valid/invalid/generation-changed cases.

}
originalHCP := hcp.DeepCopy()
meta.SetStatusCondition(&hcp.Status.Conditions, metav1.Condition{
if err := statuspatching.PatchStatusCondition(ctx, r.Client, hcp, &hcp.Status.Conditions, metav1.Condition{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: The timeout decision uses the earlier resourcesDestroyedCond, but PatchStatusCondition then fetches current status and unconditionally writes False. If HCCO concurrently sets CloudResourcesDestroyed=True, this overwrites that successful result with CloudResourcesDeletionTimedOut. The previous optimistic-lock patch would conflict and requeue. Please re-evaluate the condition and timeout against the refreshed object inside the mutation, and add a concurrent-update test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Re-evaluate against the refreshed object inside the mutation: the closure now re-checks CloudResourcesDestroyed on the freshly re-fetched hcp and skips patching entirely if HCCO already set it True, instead of blindly overwriting with the timeout condition. Added a concurrent-update test case using a Get interceptor that injects and persists the concurrent True write on the first fetch only, so the test's own verification read reflects real stored state.

meta.SetStatusCondition(&hcp.Status.Conditions, *condition)

if err := r.Client.Status().Patch(ctx, hcp, client.MergeFromWithOptions(originalHCP, client.MergeFromWithOptimisticLock{})); err != nil {
if err := statuspatching.PatchStatus(ctx, r.Client, hcp, func() error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: sgID, condition, and creationErr were computed using the earlier HCP, but PatchStatus applies them to a freshly fetched object and retries until successful. If the relevant AWS configuration changes during the cloud calls, the old security-group result can be attached to the new HCP state. Please guard the evaluated generation/spec and rerun the reconciliation when it changes, with a conflict test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Captured hcp.Generation right before the AWS calls, and guarded the PatchStatus closure the same way as reconcileValidIDPConfigurationCondition: if generation changed while creating the SG, return an error instead of attaching the stale result to the new spec state — forces a full re-reconcile, which is safe since createAWSDefaultSecurityGroup searches by infraID tag before creating (idempotent). Added TestReconcileDefaultSecurityGroup_GenerationConflict.

if currentState == hyperv1.EncryptionMigrationStateCompleted && previousState != currentState {
recordMigrationDuration(r.hcpNamespace, r.hcpName, hcp.Status.SecretEncryption)
}
if patchErr := statuspatching.PatchStatus(ctx, r.cpClient, hcp, func() error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: workingCopy is computed once from the initially fetched HCP, then copied into whatever object PatchStatus fetches during each retry. A concurrent update to SecretEncryption or EtcdDataEncryptionUpToDate can therefore be silently overwritten—the exact failure this migration intends to prevent. Please rerun the status calculation from refreshed state or let the full reconcile retry instead of replaying this snapshot. Add a conflict test that verifies concurrent state is preserved, plus the required patch-error path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Traced this carefully before deciding how to fix it. hcp.Status.SecretEncryption and the EtcdDataEncryptionUpToDate condition are written exclusively by this one reconciler for this HCP — repo-wide grep confirms no other controller writes either field (the only other references, in hostedcluster_controller.go/reconcile_legacy.go, are read-only FindStatusCondition calls bubbling it up to HostedCluster status). Combined with controller-runtime's per-key reconcile serialization (workqueue defers Add() on a key already in processing until Done()), there's no concurrent writer that could actually be clobbered here — replaying workingCopy on a PatchStatus retry only ever re-applies our own already-correct value onto our own object. Rather than adding an unneeded redesign, documented this single-owner invariant in a comment so a future second writer would prompt someone to revisit it, and added TestReconcile_PatchStatusError for the patch-error path you flagged as missing. Happy to discuss further if you see a writer I missed.

@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Aug 24, 2026
…ching helpers

Migrate 9 status patch call sites across hostedcontrolplane_controller.go
and reencryption.go to use the shared statuspatching package, adding
retry-on-conflict and consistent optimistic locking.

- hostedcontrolplane_controller.go: 7 sites migrated to PatchStatus /
  PatchStatusCondition (reconcileDeletion, update, reconcileValidIDP,
  removeCloudResources, reconcileDefaultSecurityGroup)
- reencryption.go: 1 site migrated to PatchStatus
- 2 batch-patch sites (lines 686, 869) deferred - they accumulate
  status changes across the full reconcile loop and need restructuring

Additionally guards reconcileValidIDPConfigurationCondition and
reconcileDefaultSecurityGroup against a stale hcp.Generation, since
PatchStatus's internal retry replays a precomputed value onto whatever
fresh state it fetches; a spec change mid-flight must not be silently
applied. removeCloudResources' timeout branch re-checks
CloudResourcesDestroyed against the freshly fetched object before
overwriting it, since HCCO can concurrently set it to True.

Signed-off-by: Vimal Solanki <vsolanki@redhat.com>
@vsolanki12
vsolanki12 force-pushed the vsolanki/cntrlplane-3532-tier2-optimistic-lock branch from 4151250 to 48712e0 Compare August 24, 2026 13:16
@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Aug 24, 2026
@openshift-ci-robot

openshift-ci-robot commented Aug 24, 2026

Copy link
Copy Markdown

@vsolanki12: This pull request references CNTRLPLANE-3532 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 story to target the "5.1.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Migrates 9 status patch call sites in the CPO to use statuspatching.PatchStatus / PatchStatusCondition, adding retry-on-conflict and consistent optimistic locking.

  • hostedcontrolplane_controller.go: 7 sites migrated (reconcileDeletion, update, reconcileValidIDPConfigurationCondition, removeCloudResources, reconcileDefaultSecurityGroup)
  • reencryption.go: 1 site migrated (HCCO re-encryption controller)
  • 2 batch-patch sites deferred — they accumulate status across the full reconcile loop and require restructuring

Part of the broader CNTRLPLANE-3532 migration. Depends on PR #8782 (merged).

This PR alone does not close CNTRLPLANE-3532. Remaining work tracked as follow-up PRs:

  • HCCO resources.go: destroyCloudResources's CloudResourcesDestroyed condition patch is still raw MergeFromWithOptimisticLock, not yet on the shared helper (the other two HCCO sites were already migrated via OCPBUGS-93462: Fix stale resourceVersion in HCCO patchHCPStatusCondition #8902).
  • Route status patch in removeHCPIngressFromRoutes (hostedcontrolplane_controller.go) — currently bare client.MergeFrom with no optimistic lock at all.
  • support/statuspatching JSON Patch (RFC 6902) variant + nullable-field test (Jira AC, not yet implemented).
  • Static analysis linter + make lint integration.
  • AGENTS.md guidance update.

Which issue(s) this PR fixes:

Part of CNTRLPLANE-3532 — does not fully close it, see remaining work above.

Special notes for your reviewer:

  • 2 batch-patch sites (lines 686, 869 in hostedcontrolplane_controller.go) are intentionally skipped — they accumulate status changes from the entire reconcile loop and migrating them requires restructuring the reconcile flow.
  • The originalHostedControlPlane parameter was removed from reconcileDeletion since the migrated helpers handle re-fetching internally.
  • Per @bryan-cox's review: reconcileValidIDPConfigurationCondition and reconcileDefaultSecurityGroup now guard against a stale hcp.Generation (spec changed mid-flight) before patching, rather than blindly re-applying a value computed from stale state on PatchStatus's internal retry. removeCloudResources's timeout branch re-checks CloudResourcesDestroyed against the freshly re-fetched object before overwriting it, since HCCO can concurrently set it to True. reencryption.go's workingCopy pattern was reviewed for the same class of bug — confirmed no live race (single-owner fields, controller-runtime serializes reconciles per key) and documented the invariant in a code comment; added the requested patch-error-path test.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • Bug Fixes

  • Improved reliability of control plane status updates during AWS security-group operations, release-image checks, identity-provider validation, and cloud-resource cleanup.

  • AWS security-group identifiers are now retained after successful creation without overwriting existing platform status.

  • Improved persistence and accuracy of secret-encryption migration status, conditions, completion reporting, and migration metrics.

  • Tests

  • Added coverage for security-group failures, deletion status persistence, identity-provider readiness, and platform-status updates.

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.

@vsolanki12

vsolanki12 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@bryan-cox Addressed all 4 inline findings individually (generation guards on reconcileValidIDPConfigurationCondition/reconcileDefaultSecurityGroup, fresh-state re-check on removeCloudResources, traced+declined the reencryption.go one with evidence — see inline reply).

On the broader points:

  • Added TestReconcileValidIDPConfigurationCondition and the reencryption.go patch-error-path test as requested. TestUpdate I'm holding off on — update() orchestrates ~15 sub-reconcilers (infra, PKI, etcd, the full CPOv2 component framework) and the only new logic this PR adds there is the 5-line PatchStatus closure for ValidReleaseInfo; a full happy-path unit test for update() itself is a pre-existing gap spanning way beyond this migration's diff. Open to a narrower test if you want something specific covered, or a follow-up ticket for full update() coverage.
  • Reworded the PR description: changed 'Fixes CNTRLPLANE-3532' to 'Part of CNTRLPLANE-3532', and listed the concrete remaining Jira ACs (HCCO's last resources.go site, the Route status patch in removeHCPIngressFromRoutes, the JSON Patch/RFC 6902 variant, the linter, AGENTS.md) as follow-up work, not silently implied as done.
  • Commit subject reworded to drop the Jira ID per DEVELOPMENT.md.

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 25, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws
/test e2e-v2-azure-self-managed
/test e2e-v2-gke

@openshift-ci

openshift-ci Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cblecker, vsolanki12

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

The pull request process is described 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

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 25, 2026
@vsolanki12

Copy link
Copy Markdown
Contributor Author

/test e2e-aws
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-gke

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/test e2e-aws

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/test e2e-v2-gke

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/retest-required

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-v2-azure-self-managed/

The test that failed was run before I increased the node size of the root ci cluster. There were too many pods for the kubelet errors.

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-v2-azure-self-managed

The test that failed was run before I increased the node size of the root ci cluster. There were too many pods for the kubelet errors.

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/retest

@vismishr

Copy link
Copy Markdown
Contributor

/verified by @vismishr

all results in https://vismishr.github.io/pr-8966-test-verification.html , tested on live cluster

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Aug 27, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@vismishr: This PR has been marked as verified by @vismishr.

Details

In response to this:

/verified by @vismishr

all results in https://vismishr.github.io/pr-8966-test-verification.html , tested on live cluster

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-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD b6599be and 2 for PR HEAD 48712e0 in total

@openshift-ci

openshift-ci Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

@vsolanki12: all tests passed!

Full PR test history. Your PR dashboard.

Details

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 kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
openshift-merge-bot Bot merged commit 79f9d3c into openshift:main Aug 27, 2026
45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/api Indicates the PR includes changes for the API area/ci-tooling Indicates the PR includes changes for CI or tooling area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform area/platform/gcp PR/issue for GCP (GCPPlatform) platform area/platform/kubevirt PR/issue for KubeVirt (KubevirtPlatform) platform area/platform/openstack PR/issue for OpenStack (OpenStackPlatform) platform area/platform/powervs PR/issue for PowerVS (PowerVSPlatform) platform area/testing Indicates the PR includes changes for e2e testing jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants