Skip to content

CNTRLPLANE-507: Add HCP finalizer to AWSEndpointService reconciler - #8499

Closed
hypershift-jira-solve-ci[bot] wants to merge 4 commits into
openshift:mainfrom
hypershift-community:fix-CNTRLPLANE-507
Closed

CNTRLPLANE-507: Add HCP finalizer to AWSEndpointService reconciler#8499
hypershift-jira-solve-ci[bot] wants to merge 4 commits into
openshift:mainfrom
hypershift-community:fix-CNTRLPLANE-507

Conversation

@hypershift-jira-solve-ci

@hypershift-jira-solve-ci hypershift-jira-solve-ci Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

Adds a finalizer on the HostedControlPlane resource from the AWSEndpointService reconciler to prevent HCP deletion before AWS PrivateLink resources are cleaned up.

Problem: When the CPO restarts during deletion of a SharedVPC cluster, the clientBuilder is uninitialized and the HCP (with its cross-account role ARNs) may already be deleted. This causes the reconciler to fail creating AWS clients, and after a 10-minute grace period the hypershift-operator force-removes the CPO finalizer — orphaning VPC endpoints, security groups, and DNS records in the shared VPC account.

Solution: The new HCP finalizer (hypershift.openshift.io/aws-private-link-endpoint-cleanup) follows the same pattern used by the Azure PLS controller:

  • Adds the finalizer to the HCP during normal reconciliation
  • When HCP deletion is detected, initializes AWS clients from the still-available HCP
  • Cleans up each AWSEndpointService's AWS resources and removes CR finalizers
  • Removes the HCP finalizer only after all AWSEndpointService CRs are cleaned up
  • Extends the HCP watch handler (enqueueOnHCPChange) to also trigger reconciliation when an HCP is being deleted with the finalizer present

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/CNTRLPLANE-507

Special notes for your reviewer:

  • This follows the same finalizer pattern already established by the Azure PLS controller
  • The enqueueOnHCPChange handler (renamed from enqueueOnAccessChange) now triggers on both EndpointAccess changes and HCP deletions with the finalizer
  • AWS client initialization during HCP deletion reuses the existing getAWSClient helper, sourcing credentials from the still-available HCP spec

Checklist:

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

Always review AI generated responses prior to use.
Generated with Claude Code via /jira:solve [CNTRLPLANE-507](https://redhat.atlassian.net/browse/CNTRLPLANE-507)


Note: This PR was auto-generated by the jira-agent periodic CI job in response to CNTRLPLANE-507. See the full report for token usage, cost breakdown, and detailed phase output.

Summary by CodeRabbit

  • Bug Fixes

    • Improved AWS PrivateLink deletion cleanup by coordinating finalizers between HostedControlPlane (HCP) and related endpoint service CRs.
    • Ensured HCP deletion reconciliation runs reliably across controller restarts, without racing the endpoint-service deletion path.
    • Added safer requeue behavior on Kubernetes conflicts and dependency-violation scenarios to avoid premature finalizer removal.
  • Tests

    • Added unit tests validating HCP finalizer management, deletion cleanup coordination, reconciliation request mapping/enqueue logic, and error/requeue handling for Kubernetes and AWS failures.

@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 commented May 13, 2026

Copy link
Copy Markdown

@hypershift-jira-solve-ci[bot]: This pull request references CNTRLPLANE-507 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:

Adds a finalizer on the HostedControlPlane resource from the AWSEndpointService reconciler to prevent HCP deletion before AWS PrivateLink resources are cleaned up.

Problem: When the CPO restarts during deletion of a SharedVPC cluster, the clientBuilder is uninitialized and the HCP (with its cross-account role ARNs) may already be deleted. This causes the reconciler to fail creating AWS clients, and after a 10-minute grace period the hypershift-operator force-removes the CPO finalizer — orphaning VPC endpoints, security groups, and DNS records in the shared VPC account.

Solution: The new HCP finalizer (hypershift.openshift.io/aws-private-link-endpoint-cleanup) follows the same pattern used by the Azure PLS controller:

  • Adds the finalizer to the HCP during normal reconciliation
  • When HCP deletion is detected, initializes AWS clients from the still-available HCP
  • Cleans up each AWSEndpointService's AWS resources and removes CR finalizers
  • Removes the HCP finalizer only after all AWSEndpointService CRs are cleaned up
  • Extends the HCP watch handler (enqueueOnHCPChange) to also trigger reconciliation when an HCP is being deleted with the finalizer present

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/CNTRLPLANE-507

Special notes for your reviewer:

  • This follows the same finalizer pattern already established by the Azure PLS controller
  • The enqueueOnHCPChange handler (renamed from enqueueOnAccessChange) now triggers on both EndpointAccess changes and HCP deletions with the finalizer
  • AWS client initialization during HCP deletion reuses the existing getAWSClient helper, sourcing credentials from the still-available HCP spec

Checklist:

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

Always review AI generated responses prior to use.
Generated with Claude Code via /jira:solve [CNTRLPLANE-507](https://redhat.atlassian.net/browse/CNTRLPLANE-507)

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-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label May 13, 2026
@openshift-ci openshift-ci Bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/needs-area labels May 13, 2026
@coderabbitai

coderabbitai Bot commented May 13, 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

This change adds an HCP-scoped AWS PrivateLink finalizer, updates HostedControlPlane event handling to enqueue AWSEndpointService reconciliations, and splits reconciliation into normal and HCP-deletion paths. The deletion path initializes AWS clients from the HCP, cleans up AWS resources, removes the AWSEndpointService finalizer, and clears the HCP finalizer after dependent CRs are done. Tests cover finalizer patching, deletion handling, client errors, and mapping behavior.

Possibly related PRs

  • openshift/hypershift#7868: Also changes awsprivatelink_controller.go deletion reconciliation and AWS cleanup/retry behavior around DependencyViolation.

Suggested reviewers

  • devguyio
  • enxebre
  • muraee

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Container-Privileges ❌ Error New YAML manifests add hostPID/hostNetwork/privileged and allowPrivilegeEscalation=true in kubelet-config, kubevirt CSI, and e2e pod files. Remove or justify the privileged settings; use least-privilege securityContext where possible, or isolate these manifests with explicit exemption.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding an HCP finalizer to the AWSEndpointService reconciler.
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 Added test titles are static and descriptive; no new titles embed generated names, timestamps, UUIDs, or other run-to-run values.
Test Structure And Quality ✅ Passed Tests are table-driven fake-client unit tests, with no Ginkgo waits or cluster resources; they follow the repo’s existing testing style.
Topology-Aware Scheduling Compatibility ✅ Passed Only AWSEndpointService reconciliation/finalizer logic changed; no node selectors, affinity, spread constraints, replicas, or manifests were added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Only Go unit tests were added; no new Ginkgo e2e tests or external-network dependencies were introduced.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret/token comparisons appear in the changed controller/test code.
No-Sensitive-Data-In-Logs ✅ Passed PASS: The added HCP-finalizer logs are generic status messages; I found no passwords, tokens, PII, or other clearly sensitive data in the new logging.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci

openshift-ci Bot commented May 13, 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

@openshift-ci openshift-ci Bot added area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/platform/aws PR/issue for AWS (AWSPlatform) platform and removed do-not-merge/needs-area labels May 13, 2026
@codecov

codecov Bot commented May 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 36 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.59%. Comparing base (8b5103a) to head (a2f22c7).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
...ollers/awsprivatelink/awsprivatelink_controller.go 77.77% 29 Missing and 7 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8499      +/-   ##
==========================================
+ Coverage   43.50%   43.59%   +0.08%     
==========================================
  Files         771      771              
  Lines       95722    95786      +64     
==========================================
+ Hits        41648    41760     +112     
+ Misses      51174    51121      -53     
- Partials     2900     2905       +5     
Files with missing lines Coverage Δ
...trollers/hostedcluster/hostedcluster_controller.go 55.18% <ø> (+0.45%) ⬆️
...ollers/awsprivatelink/awsprivatelink_controller.go 48.15% <77.77%> (+6.50%) ⬆️
Flag Coverage Δ
cmd-support 37.12% <ø> (ø)
cpo-hostedcontrolplane 45.51% <ø> (ø)
cpo-other 45.61% <77.77%> (+0.51%) ⬆️
hypershift-operator 53.73% <ø> (+0.07%) ⬆️
other 32.08% <ø> (ø)

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.

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Re: #8499 (comment)

Thanks for the walkthrough summary — the description and sequence diagram accurately capture the changes.

Regarding the docstring coverage warning: noted. This is a new controller flow with several helper functions; docstrings can be added in a follow-up if the team considers it valuable for this area.


AI-assisted response via Claude Code

@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.

Staff Engineer Review: Add HCP finalizer to AWSEndpointService reconciler

The core idea of this PR is correct and addresses a real operational problem — preventing AWS PrivateLink resource orphaning when the HCP is deleted before the AWSEndpointService cleanup runs. However, the implementation diverges from the Azure PLS pattern in ways that introduce correctness risks.

Blocking Issues (3)

See inline comments for details on:

  1. Dual deletion paths compete — existing CR deletion path and new HCP deletion path both remove the CR finalizer
  2. Multi-CR coordination under concurrency — convergent but produces unnecessary work with MaxConcurrentReconciles: 10
  3. UpdateFunc misses HCP deletions on controller restart — defeats the purpose of the PR

Open Questions (2)

  • Does the hypershift-operator's force-finalizer-removal logic (10-minute grace) know about this new aws-private-link-endpoint-cleanup finalizer? If not, the HCP could get stuck indefinitely.
  • The finalizer is added for ALL AWS PrivateLink clusters, not just SharedVPC. Is the broader scope intentional?

Praise

Test coverage is excellent — 784 lines of well-structured table-driven tests with gomock and client interceptors covering all new paths. The context.Background()ctx fix in the handler is a good improvement.

MaxConcurrentReconciles: 10,
}).
Watches(&hyperv1.HostedControlPlane{}, handler.Funcs{UpdateFunc: r.enqueueOnAccessChange(mgr)}).
Watches(&hyperv1.HostedControlPlane{}, handler.Funcs{UpdateFunc: r.enqueueOnHCPChange(mgr)}).

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] UpdateFunc misses HCP deletions on controller restart

Using handler.Funcs{UpdateFunc: ...} means only Update events trigger this handler. If the CPO restarts while an HCP is being deleted (DeletionTimestamp already set), the informer cache sync generates a Create event — not an Update — so this handler never fires.

The Azure PLS controller avoids this by using handler.EnqueueRequestsFromMapFunc(...), which receives all event types (Create, Update, Delete) from the informer. On restart, it gets a Create event for the HCP with DeletionTimestamp set and correctly enqueues the CRs.

With the current approach, if the CPO restarts mid-HCP-deletion, the new handler will NOT fire. The reconciler would fall through to the existing AWSEndpointService CR deletion path — exactly the scenario this PR is trying to fix.

Recommendation: Switch to handler.EnqueueRequestsFromMapFunc(...) to match the Azure PLS pattern.

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. Switched from handler.Funcs{UpdateFunc: ...} to handler.EnqueueRequestsFromMapFunc(...) matching the Azure PLS pattern. The new mapHCPToAWSEndpointService() function receives all event types (Create, Update, Delete), so on controller restart the informer cache sync's Create event now correctly triggers cleanup for an HCP with DeletionTimestamp already set.

The EndpointAccess change detection (previously via old/new comparison in UpdateFunc) is dropped from the handler — those changes are picked up by the reconciler's existing 5-minute periodic requeue, which is acceptable since EndpointAccess changes are rare operational events.


AI-assisted response via Claude Code

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. Switched from handler.Funcs{UpdateFunc: ...} to handler.EnqueueRequestsFromMapFunc(r.mapHCPToAWSEndpointService()) matching the Azure PLS pattern exactly. The new mapHCPToAWSEndpointService() MapFunc receives all event types (Create, Update, Delete), so on controller restart the informer cache sync's Create event correctly triggers cleanup for an HCP with DeletionTimestamp already set.

The MapFunc filters by finalizer presence (controllerutil.ContainsFinalizer(hcp, hcpAWSPrivateLinkFinalizerName)) to avoid unnecessary reconciliations, matching the Azure PLS approach. EndpointAccess change detection is dropped from the handler — those changes are picked up by the reconciler's existing 5-minute periodic requeue.

Tests updated: replaced TestEnqueueOnHCPChange (which tested the old UpdateFunc) with TestMapHCPToAWSEndpointService (which tests the new MapFunc directly).


AI-assisted response via Claude Code

// Handle HCP deletion: clean up AWS resources while HCP credentials are still valid.
if !hcp.DeletionTimestamp.IsZero() {
return r.reconcileHCPDeletion(ctx, awsEndpointService, hcp, log)
}

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] Dual deletion paths can compete

The existing AWSEndpointService CR deletion path (lines 466-486 in the diff) runs when the CR itself has a DeletionTimestamp and also removes the CR finalizer + calls r.delete(). This new HCP deletion path at line 534 also removes the CR finalizer + calls r.delete().

These two paths can activate simultaneously during namespace deletion or HCP ownership-based cascading. Consider:

  1. HCP deletion triggers enqueueOnHCPChange, enqueuing all CRs
  2. Namespace/owner cascade sets DeletionTimestamp on the CRs themselves
  3. A reconcile fires for a CR that has BOTH its own DeletionTimestamp AND the HCP is being deleted
  4. The CR enters the existing deletion path (step 1), which initializes from HCP and cleans up
  5. Another reconcile enters this HCP deletion path

The existing CR deletion path (line 466) does return early before reaching this check, so they are technically exclusive within a single reconcile call. But with MaxConcurrentReconciles: 10, two concurrent reconciles for the same CR could race.

Suggestion: Add an explicit guard here: if !awsEndpointService.DeletionTimestamp.IsZero() { return ctrl.Result{}, nil } to make the exclusion explicit and defend against concurrent reconciles.

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. Added explicit guard at the top of reconcileHCPDeletion:

if !awsEndpointService.DeletionTimestamp.IsZero() {
    return ctrl.Result{}, nil
}

This makes the exclusion between the two deletion paths explicit and defends against concurrent reconciles under MaxConcurrentReconciles: 10. If the CR itself is being deleted, we defer to the existing CR deletion path at the top of Reconcile.


AI-assisted response via Claude Code

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. Added explicit guard at the top of reconcileHCPDeletion:

if !awsEndpointService.DeletionTimestamp.IsZero() {
    return ctrl.Result{}, nil
}

This makes the exclusion between the two deletion paths explicit and defends against concurrent reconciles under MaxConcurrentReconciles: 10. If the CR itself is being deleted, we defer to the existing CR deletion path at the top of Reconcile.


AI-assisted response via Claude Code

// our finalizer blocks HCP deletion.
r.awsClientBuilder.initializeWithHCP(log, hcp)
ec2Client, route53Client, err := r.awsClientBuilder.getClients(ctx)
if 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] Multi-CR coordination needs documentation or simplification

With MaxConcurrentReconciles: 10 and enqueueOnHCPChange enqueuing ALL CRs, multiple reconcilers race through reconcileHCPDeletion concurrently. Each one cleans up its own CR, then checks if all others are done. The last one to finish removes the HCP finalizer, while earlier finishers return RequeueAfter: 5s and re-enter this path only to find the HCP finalizer already removed.

This convergent pattern is functionally correct, but:

  1. It produces unnecessary requeues and reconcile loops
  2. It is not documented, making it hard for future maintainers to reason about
  3. The Azure PLS controller avoids this entirely because it has MaxConcurrentReconciles: 1 and only one CR per namespace

Suggestion: At minimum, add a comment explaining the convergent behavior. Alternatively, consider having only the CR whose cleanup triggers len(pendingCRs) == 0 remove the HCP finalizer, and have all others simply return ctrl.Result{} after their own cleanup.

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. Added comprehensive documentation on the reconcileHCPDeletion function explaining the convergent multi-CR coordination pattern:

  • Multiple reconcilers run concurrently (one per AWSEndpointService CR)
  • Each cleans up its own CR, then checks if all CRs are done
  • Only the last reconciler to finish removes the HCP finalizer
  • Earlier finishers see pending CRs, return RequeueAfter, and on re-entry find the finalizer already removed

The comment explains this produces a small number of no-op requeues but is correct and self-healing. This is functionally similar to how the Azure PLS controller works, but documented explicitly because the AWS controller has MaxConcurrentReconciles: 10 and multiple CRs per namespace (unlike Azure PLS's MaxConcurrentReconciles: 1 with one CR per namespace).


AI-assisted response via Claude Code

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. Added comprehensive documentation on reconcileHCPDeletion explaining the convergent multi-CR coordination pattern:

  • Multiple reconcilers run concurrently (one per AWSEndpointService CR) under MaxConcurrentReconciles: 10
  • Each cleans up its own CR's AWS resources, removes the CR finalizer, then checks if all CRs are done
  • The last reconciler to finish (seeing len(pendingCRs) == 0) removes the HCP finalizer
  • Earlier finishers see pending CRs, return RequeueAfter, and on re-entry find the HCP finalizer already removed

The comment explicitly contrasts this with the Azure PLS controller (MaxConcurrentReconciles: 1, one CR per namespace) to explain why this convergent pattern is necessary for the AWS controller.


AI-assisted response via Claude Code

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.

One detail on the extra requeue: the r.List at the pending-CRs check reads from the informer cache, which won't have caught up with the r.Patch that just removed the CR finalizer. So the reconciler that finishes last is guaranteed to see itself as still-pending and take one extra requeue cycle. Not a real problem — just worth a brief comment, or you could skip the current AES by name in the pending-CRs loop to avoid it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Done. Opted to skip the current AES by name in the pending-CRs loop rather than just adding a comment — this eliminates the guaranteed extra requeue cycle for the last reconciler to finish.


AI-assisted response via Claude Code

controllerutil.AddFinalizer(hcp, hcpAWSPrivateLinkFinalizerName)
if err := r.Patch(ctx, hcp, client.MergeFromWithOptions(originalHCP, client.MergeFromWithOptimisticLock{})); err != nil {
if apierrors.IsConflict(err) {
return ctrl.Result{Requeue: true}, 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.

[suggestion] Use RequeueAfter: time.Second instead of Requeue: true on conflicts

The Azure PLS equivalent returns ctrl.Result{RequeueAfter: time.Second} on conflict (see controller.go line 371). Using Requeue: true risks a tight retry loop under contention when multiple AWSEndpointService reconcilers are concurrently trying to patch the same HCP.

Same applies to the conflict handling in ensureHCPFinalizer (line 558).

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 both conflict-handling sites to ctrl.Result{RequeueAfter: time.Second}:

  1. ensureHCPFinalizer (adding finalizer)
  2. reconcileHCPDeletion (removing finalizer)

Both now include a comment explaining the rationale: avoiding tight retry loops when multiple AWSEndpointService reconcilers concurrently try to patch the same HCP.

Note: the Azure PLS controller also uses Requeue: true for conflicts (controller.go line 371), so this change makes the AWS controller stricter than Azure PLS.


AI-assisted response via Claude Code

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 both conflict-handling sites to ctrl.Result{RequeueAfter: time.Second}:

  1. ensureHCPFinalizer (adding finalizer)
  2. reconcileHCPDeletion (removing finalizer)

Both now include a comment explaining the rationale: avoiding tight retry loops when multiple AWSEndpointService reconcilers concurrently try to patch the same HCP. Tests updated to assert RequeueAfter > 0 instead of Requeue == true.


AI-assisted response via Claude Code

// TestReconcileDeletionSharedVPC for details.
// The HCP finalizer (hcpAWSPrivateLinkFinalizerName) added during normal
// reconciliation ensures the HCP remains available during this cleanup.
// For SharedVPC clusters, this guarantees the cross-account role ARNs can

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.

[suggestion] Comment overstates the guarantee

This comment claims the HCP finalizer "ensures the HCP remains available during this cleanup." That is only true after a successful normal reconciliation has added the finalizer. If a cluster is newly created and the controller has not yet reconciled (e.g., controller was down), the HCP can still be deleted before the AWSEndpointService cleanup runs — the old scenario.

Consider acknowledging this edge case rather than stating the guarantee unconditionally.

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. Updated the comment to acknowledge the edge case. The new wording states that the finalizer "when present, blocks HCP deletion" and explicitly notes that it's only added after a successful normal reconciliation — if the controller hasn't reconciled yet (e.g., was down since cluster creation), the HCP may be deleted before the finalizer is placed, and the best-effort initialization is the only protection in that case.


AI-assisted response via Claude Code

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. Updated the comment to acknowledge the edge case. The new wording states that the finalizer, "when present, blocks HCP deletion" and explicitly notes that it's only added after a successful normal reconciliation — if the controller hasn't reconciled yet (e.g., was down since cluster creation), the HCP may be deleted before the finalizer is placed, and the best-effort initialization is the only protection in that case.


AI-assisted response via Claude Code

}

// Enqueue when EndpointAccess changes (existing behavior).
if newHCP.Spec.Platform.AWS != nil && oldHCP.Spec.Platform.AWS != nil && newHCP.Spec.Platform.AWS.EndpointAccess != oldHCP.Spec.Platform.AWS.EndpointAccess {

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.

[suggestion] Filter deletion trigger to transition only

Once the HCP finalizer is added, ANY HCP update with a DeletionTimestamp will re-enqueue all CRs. During HCP deletion, status updates from other controllers will repeatedly trigger this, producing unnecessary list+enqueue cycles.

Consider adding oldHCP.DeletionTimestamp.IsZero() to the condition so it only fires on the transition to deletion:

if oldHCP.DeletionTimestamp.IsZero() && !newHCP.DeletionTimestamp.IsZero() && controllerutil.ContainsFinalizer(newHCP, hcpAWSPrivateLinkFinalizerName) {

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.

Addressed by switching to `EnqueueRequestsFromMapFunc` (per comment 1). With the new `mapHCPToAWSEndpointService()` MapFunc, we can no longer detect the deletion transition (old vs new DeletionTimestamp) because MapFunc only receives the current object, not old/new.

However, the filtering is achieved differently: the MapFunc only fires when the HCP has our finalizer (`controllerutil.ContainsFinalizer(hcp, hcpAWSPrivateLinkFinalizerName)`). Once all CRs are cleaned up and the HCP finalizer is removed, subsequent HCP updates no longer trigger CR enqueues. During the short deletion window, the repeated enqueues from status updates are harmless since the reconciler is idempotent — CRs that are already cleaned up return early. This matches the Azure PLS pattern exactly.


AI-assisted response via Claude Code

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.

Addressed by switching to EnqueueRequestsFromMapFunc (per comment 1). With the new mapHCPToAWSEndpointService() MapFunc, we can no longer detect the deletion transition (old vs new DeletionTimestamp) because MapFunc only receives the current object, not old/new.

However, the filtering is achieved differently: the MapFunc only fires when the HCP has our finalizer (controllerutil.ContainsFinalizer(hcp, hcpAWSPrivateLinkFinalizerName)). Once all CRs are cleaned up and the HCP finalizer is removed, subsequent HCP updates no longer trigger CR enqueues. During the short deletion window, the repeated enqueues from status updates are harmless since the reconciler is idempotent — CRs that are already cleaned up return early. This matches the Azure PLS pattern exactly.


AI-assisted response via Claude Code

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.

One additional thing to consider: after the finalizer is placed, every HCP status update (from other controllers) will trigger reconciliation of all AES CRs, since the MapFunc only filters on finalizer presence. Before the finalizer is placed, EndpointAccess changes aren't detected at all. The comment at line 405-406 acknowledges the deferred detection, and the over-triggering is bounded by the existing MaxConcurrentReconciles: 10 -- but it's a behavioral change from the old selective approach worth keeping in mind for AWS API call volume.

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. The over-triggering is bounded by MaxConcurrentReconciles: 10 and each extra reconcile is cheap (reads from informer cache, checks finalizer state). Agree this is worth keeping in mind for AWS API call volume — the actual AWS API calls only happen in the normal reconcile path which has the 5 * time.Minute requeue guard.


AI-assisted response via Claude Code

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. The over-triggering from HCP status updates is bounded by MaxConcurrentReconciles: 10 and the reconciler returns early for non-deletion events when there are no AWS resource changes. The periodic 5-minute requeue already handles EndpointAccess drift detection.


AI-assisted response via Claude Code

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. The over-triggering is bounded by MaxConcurrentReconciles: 10 and the reconciler returns early at multiple checkpoints (no serviceName, HCP not deleting, etc.) so the additional reconciliations are cheap no-ops. The tradeoff is acceptable given that the alternative (selective HCP field watching) would miss the DeletionTimestamp change on controller restart.


AI-assisted response via Claude Code

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Re: #8499 (review)

@bryan-cox Thank you for the thorough staff engineer review. All blocking issues and suggestions have been addressed — see inline replies for details on each change.

Summary of changes

# Comment Action
1 [blocking] UpdateFunc misses HCP deletions on restart Switched to EnqueueRequestsFromMapFunc matching Azure PLS pattern
2 [blocking] Dual deletion paths can compete Added DeletionTimestamp.IsZero() guard in reconcileHCPDeletion
3 [blocking] Multi-CR coordination needs documentation Added comprehensive convergent-pattern docs on reconcileHCPDeletion
4 [suggestion] Use RequeueAfter on conflicts Changed to RequeueAfter: time.Second in both conflict handlers
5 [suggestion] Comment overstates guarantee Acknowledged edge case where finalizer isn't yet placed
6 [suggestion] Filter deletion trigger Handled via finalizer-based filtering in new MapFunc (matches Azure PLS)

Answers to open questions

Q: Does the hypershift-operator's force-finalizer-removal logic know about this new finalizer?
The HO's deleteAWSEndpointServices function handles the CPO finalizer on AWSEndpointService CRs (the 10-minute grace period), not HCP-level finalizers. The new aws-private-link-endpoint-cleanup finalizer is on the HCP itself. There is no HO-side force-removal for this finalizer — it relies on the CPO being healthy to clean up and remove it. This is the same design as the Azure PLS controller's azure-pls-endpoint-cleanup HCP finalizer, which also has no HO-side fallback. If the CPO is permanently down, the HCP would get stuck. For production safety, this could be addressed separately by adding HCP-level finalizer timeout logic to the HO, but that would be a cross-platform concern affecting both AWS and Azure.

Q: Is the broader scope (all AWS PrivateLink clusters, not just SharedVPC) intentional?
Yes, intentional. While the orphan risk is most acute for SharedVPC clusters (where AWS clients fail entirely without the HCP's role ARNs), non-SharedVPC clusters also benefit: the finalizer ensures the clientBuilder can always be re-initialized from the HCP on controller restart during deletion, rather than relying on the best-effort initialization path. The Azure PLS controller applies its HCP finalizer to all Azure PLS clusters regardless of configuration, which is the pattern we're following.


AI-assisted response via Claude Code

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Re: #8499 (review)

Thank you for the thorough staff engineer review, @bryan-cox. All 6 inline comments have been addressed:

Blocking Issues (3) — All Fixed

  1. UpdateFunc misses HCP deletions on controller restart — Switched from handler.Funcs{UpdateFunc: ...} to handler.EnqueueRequestsFromMapFunc(r.mapHCPToAWSEndpointService()) matching the Azure PLS pattern. Create events from informer cache sync now correctly trigger cleanup.

  2. Dual deletion paths can compete — Added explicit DeletionTimestamp guard at the top of reconcileHCPDeletion. If the CR itself is being deleted, we defer to the existing CR deletion path.

  3. Multi-CR coordination needs documentation — Added comprehensive comment block on reconcileHCPDeletion documenting the convergent pattern: multiple reconcilers run concurrently, each cleans up its own CR, and the last one to finish removes the HCP finalizer. Explicitly contrasts with Azure PLS's simpler model.

Suggestions (3) — All Fixed

  1. Use RequeueAfter: time.Second on conflicts — Changed both ensureHCPFinalizer and reconcileHCPDeletion conflict handlers. Tests updated accordingly.

  2. Comment overstates the guarantee — Updated comment to say "when present, blocks HCP deletion" and acknowledges the controller-hasn't-reconciled-yet edge case.

  3. Filter deletion trigger to transition only — Handled by the switch to EnqueueRequestsFromMapFunc. The MapFunc filters by finalizer presence, and once the finalizer is removed, no more enqueues occur.

All tests pass. Ready for re-review.


AI-assisted response via Claude Code

@bryan-cox

Copy link
Copy Markdown
Member

/test e2e-aws

@bryan-cox

Copy link
Copy Markdown
Member

e2e-aws evidence that the HCP finalizer is working

Job: pull-ci-openshift-hypershift-main-e2e-aws/2067264178355179520

Finalizer added during reconciliation (CPO logs)

Both private cluster tests show the CPO logging "Adding HCP finalizer for AWS PrivateLink cleanup":

TestCreateClusterPrivateCPO log (grep for Adding HCP finalizer):

{"level":"info","ts":"2026-06-17T17:02:21Z","msg":"Adding HCP finalizer for AWS PrivateLink cleanup","controller":"awsendpointservice","AWSEndpointService":{"name":"kube-apiserver-private","namespace":"e2e-clusters-5w84p-private-xcn67"}}

TestCreateClusterPrivateWithRouteKASCPO log (grep for Adding HCP finalizer):

{"level":"info","ts":"2026-06-17T17:03:11Z","msg":"Adding HCP finalizer for AWS PrivateLink cleanup","controller":"awsendpointservice","AWSEndpointService":{"name":"private-router","namespace":"e2e-clusters-5jz4x-private-22vkq"}}

Finalizer present on HCP resources (YAML dumps)

All three HCPs in the dump have hypershift.openshift.io/aws-private-link-endpoint-cleanup in their finalizers:

Clean teardown (destroy logs)

Both private cluster tests destroyed successfully with no errors — the finalizer did not block teardown:

Limitation

The reconcileHCPDeletion path ("HCP is being deleted" / "All AWS PrivateLink resources cleaned up, removing HCP finalizer") is not captured in the dump because the dump is collected pre-destroy and the CPO pod is terminated during destroy. The clean teardown is indirect evidence the finalizer was properly removed. The deletion path is covered by unit tests.

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

Copy link
Copy Markdown
Contributor

Tests from second stage were triggered manually. Pipeline can be controlled only manually, until HEAD changes. Use command to trigger second stage.

@openshift-ci

openshift-ci Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox, hypershift-jira-solve-ci[bot]

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 Jun 18, 2026
@bryan-cox

Copy link
Copy Markdown
Member

/lgtm cancel

@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Jun 18, 2026
@bryan-cox

Copy link
Copy Markdown
Member

/rebase

@github-actions

Copy link
Copy Markdown

🤖 Rebasing PR onto main: workflow run

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 24, 2026
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jun 24, 2026
@cblecker

cblecker commented Jul 5, 2026

Copy link
Copy Markdown
Member

/address-review-comments

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

🤖 Addressing review comments: workflow run

@bryan-cox

Copy link
Copy Markdown
Member

/restructure-commits

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Restructuring commits: workflow run

@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.

review

@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.

Overall this is well-designed -- the convergent multi-CR coordination pattern is solid, the flow ordering in Reconcile is carefully thought through, and the test coverage is thorough. One minor test gap noted inline.

}
}

func TestReconcileCRDeletion(t *testing.T) {

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.

TestReconcileCRDeletion doesn't exercise the delete() returning (false, nil) requeue path -- the case where AWS resources aren't fully cleaned up yet (e.g., DependencyViolation on security group deletion). TestReconcileHCPDeletion covers this at line 2228 with the DependencyViolation mock pattern. Adding a similar case here (AES with SecurityGroupID in status, DeleteSecurityGroup returning DependencyViolation) would cover the RequeueAfter: endpointServiceDeletionRequeueDuration branch.

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. Added a "When AWS resource cleanup is incomplete it should requeue" test case to TestReconcileCRDeletion with SecurityGroupID in status and DeleteSecurityGroup returning DependencyViolation, verifying the RequeueAfter: endpointServiceDeletionRequeueDuration branch.


AI-assisted response via Claude Code

@enxebre

enxebre commented Jul 8, 2026

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@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 HCP finalizer pattern here is correct and matches the Azure PLS controller. The issue is that deleteAWSEndpointServices() in the HC controller (hostedcluster_controller.go:3834) deletes all AWSEndpointService CRs and waits for them to be GC'd before the HCP gets its DeletionTimestamp (line 3900). By the time the HCP is deleted, no CRs exist for mapHCPToAWSEndpointService to enqueue, reconcileHCPDeletion never runs, and this finalizer is never removed. The HC controller loops at "Waiting for hostedcontrolplane deletion" indefinitely.

Azure PLS doesn't have this problem because the HC controller has no equivalent deleteAzurePrivateLinkServices() — Azure PLS CRs still exist when the HCP gets DeletionTimestamp, so the watch fires and cleanup works as designed.

deleteAWSEndpointServices() was introduced in PR #4740 (OCPBUGS-42107) as a fix for the same underlying problem — the HCP being unavailable during cleanup. It was the right solution at the time (pre-SharedVPC), but the HCP finalizer pattern is the architectural replacement, as described in CNTRLPLANE-507. The two approaches conflict because deleteAWSEndpointServices() reverses the deletion ordering that the HCP finalizer depends on.

I think the right path is to remove deleteAWSEndpointServices() from the HC controller's delete() function (lines 3833-3841) and let the CPO handle everything through reconcileHCPDeletion, aligning AWS with the Azure pattern. The 10-minute force-removal safety net in deleteAWSEndpointServices() would be replaced by the HCP finalizer itself — which is better for SharedVPC since it prevents orphaned resources rather than allowing them after a timeout.

@cblecker

cblecker commented Jul 8, 2026

Copy link
Copy Markdown
Member

@enxebre @bryan-cox --
A general comment on these bot based workflows (I've been trying to stick this one out to try and really get a sense on how this pattern compares to just working with a model locally). I've done at least 5 separate review rounds and nearly 40 comments.. and there was still a major disconnect in the logic (this is both on my part trying to understand the logic and on the part of the models trying to implement things in the smallest way possible).

This one may be a really good example of the fact that these autonomous/semi-autonomous workflows can really be time/token sinks. 😬

@cblecker

cblecker commented Jul 8, 2026

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Done. Removed deleteAWSEndpointServices() from the HC controller's delete() function (along with the now-unused awsEndpointDeletionGracePeriod constant and platformaws import). The CPO now handles all AWS PrivateLink cleanup through reconcileHCPDeletion, aligning AWS with the Azure PLS pattern.

Also aligned the CR finalizer addition to use Patch with MergeFromWithOptimisticLock per the suggestion.


AI-assisted response via Claude Code

OpenShift CI Bot and others added 3 commits July 8, 2026 19:04
…reconciler

Add a finalizer on the HostedControlPlane to block HCP deletion until
all AWS PrivateLink resources (VPC endpoints, security groups, DNS
records) are cleaned up. Without this finalizer, the controller may
not be able to construct valid AWS clients if the HCP is already
deleted — particularly for SharedVPC clusters where cross-account
role ARNs are sourced from the HCP spec — which would orphan AWS
resources.

Key changes:
- Add hcpAWSPrivateLinkFinalizerName finalizer constant and manage
  its lifecycle during reconcile/cleanup
- Replace handler.Funcs{UpdateFunc: ...} with
  EnqueueRequestsFromMapFunc so Create/Delete/Update HCP events all
  trigger reconciliation — critical for CPO restarts where a
  deleting HCP appears as a Create event after cache sync
- Guard reconciliation behind finalizer presence to avoid unnecessary
  reconciliations for unrelated HCPs
- Refactor cleanup logic into deleteAWSEndpointService to share
  between reconcile and HCP-deletion paths
- Clear localZoneID for non-SharedVPC clusters in setFromHCP
- Add comprehensive unit tests for the new finalizer behavior

Signed-off-by: OpenShift CI Bot <ci-bot@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
…tion

Address review feedback: add test case exercising the delete() returning
(false, nil) requeue path in TestReconcileCRDeletion (DependencyViolation
on security group deletion). Also add invariant comment at the pending-CRs
check in reconcileHCPDeletion documenting that CRs only get the finalizer
after serviceName is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove deleteAWSEndpointServices() from the HC controller's delete()
path. It conflicts with the HCP finalizer pattern: it deletes all
AWSEndpointService CRs before the HCP gets its DeletionTimestamp, so
mapHCPToAWSEndpointService has nothing to enqueue and reconcileHCPDeletion
never runs, leaving the HCP finalizer stuck.

With this change, the CPO handles all AWS PrivateLink cleanup through
reconcileHCPDeletion, aligning AWS with the Azure PLS pattern. The HCP
finalizer replaces the 10-minute force-removal safety net.

Also aligns CR finalizer addition to use Patch with optimistic locking
instead of Update, matching the pattern used for HCP finalizer operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci openshift-ci Bot added the area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release label Jul 8, 2026
@cblecker

cblecker commented Jul 8, 2026

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

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

Copy link
Copy Markdown
Contributor Author

Now I have all the evidence needed. The failure is clear and well-documented. Let me produce the final report:

Test Failure Analysis Complete

Job Information

Test Failure Analysis

Error

ERROR: Failed to trigger periodic job after 10 retries
All 10 attempts received HTTP 429 (Too Many Requests) from Gangway API (nginx/1.20.1)

Summary

The address-review-comments CI job failed because the hypershift-review-agent-trigger step could not trigger the periodic review-agent job via the Prow Gangway API. The script made 10 consecutive POST requests to the Gangway API endpoint (/v1/executions/periodic-ci-openshift-hypershift-main-periodic-review-agent), and every single attempt was rejected with an HTTP 429 "Too Many Requests" response from the nginx reverse proxy fronting Gangway. After exhausting all 10 retries (with 10-second intervals, ~100 seconds total), the script exited with code 1. This is an infrastructure-level rate limiting issue completely unrelated to the PR's code changes.

Root Cause

The root cause is Gangway API rate limiting on the CI infrastructure. Specifically:

  1. The step hypershift-review-agent-trigger attempts to trigger a periodic Prow job (periodic-ci-openshift-hypershift-main-periodic-review-agent) by POSTing to the Gangway API at https://gangway-ci.apps.ci.l2s4.p1.openshiftapps.com/v1/executions/{job-name}.

  2. The nginx reverse proxy (version 1.20.1) fronting the Gangway API returned HTTP 429 "Too Many Requests" on all 10 retry attempts. The response body is raw HTML from nginx — not a JSON error from Gangway itself — confirming the rate limit is enforced at the proxy/load-balancer layer, not the application layer.

  3. The retry strategy was insufficient for this rate-limiting scenario: 10 retries at 10-second intervals provides only ~100 seconds of retry window. Under sustained rate limiting (e.g., many PRs triggering simultaneously, or a global rate limit burst), this window is too short. There is no exponential backoff or jitter in the retry logic.

  4. This is a transient infrastructure issue — the Gangway API was overloaded or had its rate limit quota temporarily exhausted at the time this job ran (2026-07-08T19:59–20:01 UTC). The PR's code changes (adding an HCP finalizer to AWSEndpointService reconciler) are completely unrelated to this failure.

Recommendations
  1. Rerun the job — This is a transient infrastructure flake. Simply re-triggering ci/prow/address-review-comments should succeed once the Gangway API rate limit window resets.

  2. This failure is not related to the PR's code changes — The address-review-comments job only triggers an external review agent via the Gangway API; it does not compile, test, or validate the PR's code. The 429 rate limiting is a CI infrastructure issue.

  3. For long-term fix (CI step owners): Consider updating the hypershift-review-agent-trigger step script to use exponential backoff with jitter instead of fixed 10-second intervals, and increase the max retry count or total timeout to better tolerate sustained rate limiting.

  4. Consider marking this job as optional — If the review agent trigger is not gating for merge readiness, it could be configured as an optional/informational check to avoid blocking PRs on infrastructure flakes.

Evidence
Evidence Detail
Failed Step address-review-comments-hypershift-review-agent-trigger (test phase)
Error Message ERROR: Failed to trigger periodic job after 10 retries
HTTP Status All 10 attempts returned HTTP 429 (Too Many Requests)
Response Source nginx/1.20.1 reverse proxy (HTML response, not Gangway JSON)
Target API Gangway API: /v1/executions/periodic-ci-openshift-hypershift-main-periodic-review-agent
Retry Pattern 10 retries × 10s intervals = ~100s total retry window
Step Duration 2m11s (pod ran from 19:59:45Z to 20:01:59Z)
Exit Code 1 (script exit 1 after retry exhaustion)
Step Registry hypershift-review-agent-trigger
Failure Type CI infrastructure flake — unrelated to PR code changes

@cblecker

cblecker commented Jul 8, 2026

Copy link
Copy Markdown
Member

/test address-review-comments

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor Author

Review agent triggered. View job

With MaxConcurrentReconciles: 10, multiple reconcilers can race past
the pending-CRs check and attempt to remove the HCP finalizer. The
first succeeds; if the HCP had no other finalizers, k8s GCs it and
runner-up reconcilers get NotFound as a hard reconcile error. Handle
this gracefully by returning nil, matching the existing Conflict
handling pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci

openshift-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@hypershift-jira-solve-ci[bot]: 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.

@ironcladlou

ironcladlou commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Not blocking, just a note... the need for this seems like a pattern that will continue to emerge across other platforms and resources classes (GCP...) and may be indicative of a design gap. Tracking deletion handling across sprawling finalizers and processes could lead us to inadvertent coupling, ordering issues, too many testing dimensions, etc. Deletion handling can't be reasoned about locally (adding a new one has to consider the behavior around all the others to avoid those issues, etc).

Like, what if the main HCP reconciler's deletion path could gate the main finalizer removal on a set of status conditions (e.g. CloudResourcesCleanedUp per platform controller), where each platform controller sets its own condition to true when its cleanup is done, replacing per-controller finalizers with per-controller conditions and a single finalizer that reads them all.

Food for thought...

edit:
re: #8499 (review)

The HCP finalizer pattern here is correct and matches the Azure PLS controller.

I agree it "matches the Azure PLS controller" but as to whether the "HCP finalizer pattern here is correct" I don't necessarily agree with that assumption (hence my above notes)

@bryan-cox

Copy link
Copy Markdown
Member

/close

Closing this PR in favor of a new approach. Based on Dan's design feedback and the follow-up discussion with Cesar and Alberto, the consensus is to use HCP status conditions instead of per-controller finalizers to gate deletion cleanup. This avoids the coupling/ordering issues Dan raised and degrades gracefully via timeouts if a controller can't run.

New ticket: CNTRLPLANE-3857

@openshift-ci openshift-ci Bot closed this Jul 21, 2026
@openshift-ci

openshift-ci Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

@bryan-cox: Closed this PR.

Details

In response to this:

/close

Closing this PR in favor of a new approach. Based on Dan's design feedback and the follow-up discussion with Cesar and Alberto, the consensus is to use HCP status conditions instead of per-controller finalizers to gate deletion cleanup. This avoids the coupling/ordering issues Dan raised and degrades gracefully via timeouts if a controller can't run.

New ticket: CNTRLPLANE-3857

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.

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/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release 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 jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants