Skip to content

fix: make frontend cluster create idempotent to prevent CS duplicate collisions - #6025

Closed
Cliff Schomburg (cssjr) wants to merge 2 commits into
Azure:mainfrom
cssjr:fix/aro-27951-idempotent-cluster-create
Closed

Cliff Schomburg (cssjr) wants to merge 2 commits into
Azure:mainfrom
cssjr:fix/aro-27951-idempotent-cluster-create

Conversation

@cssjr

@cssjr Cliff Schomburg (cssjr) commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes ARO-27951: "InvalidRequestContent — Duplicate ARO-HCP cluster name collision" hitting ~7.2% of CI E2E runs
  • Adds idempotency check to the frontend createHCPCluster() path before calling PostCluster, preventing split-brain when Cosmos write fails after CS create succeeds
  • Extracts FindClusterByAzureInfo into a shared ocm package function, deduplicating logic between frontend and backend controller

Root Cause

The frontend's createHCPCluster() performs a two-phase write that is not atomic:

  1. Phase 1: PostCluster to Cluster Service (CS) — creates the cluster record
  2. Phase 2: Cosmos DB transactional batch — creates cluster + operation documents

If Phase 2 fails (transient Cosmos error), the system enters a split-brain state: CS has the cluster, Cosmos does not. When the Azure SDK automatically retries the failed 500:

  1. CreateOrUpdateHCPCluster checks Cosmos → not found → routes to createHCPCluster
  2. PostCluster to CS → CS rejects: "Duplicate ARO-HCP cluster name" (HTTP 400)
  3. Error mapped to InvalidRequestContent → test/request fails

This was confirmed by an identical Stage incident documented in ARO-17939 (comment by Mike Gahagan, 2026-05-20).

Fix

Before calling PostCluster, the frontend now searches CS for an existing cluster with matching Azure metadata (subscription, resource group, resource name, tenant, managed resource group) via ocm.FindClusterByAzureInfo. If an orphaned CS cluster from a prior failed attempt is found, it is reused instead of creating a duplicate.

The backend's ClusterClusterServiceCreate controller already had this idempotency pattern — this PR extracts it into a shared function and applies it to the frontend as well.

Test plan

  • go build for all affected packages (internal/ocm, frontend, backend)
  • go test ./internal/ocm/... — passes
  • go test ./frontend/... — passes
  • go test ./backend/... — all 20+ controller test packages pass
  • make lint — passes
  • Integration tests (make test-integration)
  • E2E validation: monitor branch-ci-Azure-ARO-HCP-main-e2e-integration-e2e-parallel success rate after merge

🤖 Generated with Claude Code

…collisions

The frontend's createHCPCluster() performs a two-phase write: it creates
the cluster in Cluster Service (CS) first, then writes to Cosmos DB. If
the Cosmos write fails, CS has the cluster but the RP doesn't — a
split-brain state. When the Azure SDK automatically retries the failed
500, the retry calls PostCluster again and CS rejects with "Duplicate
ARO-HCP cluster name" (400 InvalidRequestContent).

This was hitting ~7.2% of CI E2E runs (ARO-27951) and was confirmed in
a Stage incident (ARO-17939).

Fix: before calling PostCluster, search CS by Azure metadata
(subscription, resource group, resource name, tenant, managed resource
group) via a new shared FindClusterByAzureInfo function. If an orphaned
CS cluster from a prior failed attempt is found, reuse it instead of
creating a duplicate. This makes the create path idempotent on retry.

Also refactors the backend's ClusterClusterServiceCreate controller to
use the same shared function, eliminating duplicated search logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@openshift-ci

openshift-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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

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

@openshift-ci

openshift-ci Bot commented Jul 10, 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

@cssjr

Copy link
Copy Markdown
Collaborator Author

/test all

@cssjr

Copy link
Copy Markdown
Collaborator Author

/assign copilot

@openshift-ci

openshift-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Cliff Schomburg (@cssjr): GitHub didn't allow me to assign the following users: copilot.

Note that only Azure members with read permissions, repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.
For more information please see the contributor guide

Details

In response to this:

/assign copilot

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.

@cssjr

Copy link
Copy Markdown
Collaborator Author

/cc copilot

@cssjr

Copy link
Copy Markdown
Collaborator Author

/retest

1 similar comment
@cssjr

Copy link
Copy Markdown
Collaborator Author

/retest

@swiencki Simon Wiencki (swiencki) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

backend/pkg/controllers/clustercreation/cluster_cluster_service_create_controller_test.go - A second cluster in a different bucket asserting no manifest is created would prove the bucketing logic actually excludes foreign work.

@cssjr

Copy link
Copy Markdown
Collaborator Author

/retest

@cssjr
Cliff Schomburg (cssjr) marked this pull request as ready for review July 13, 2026 23:21
Copilot AI review requested due to automatic review settings July 13, 2026 23:21

Copilot AI 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.

Pull request overview

This PR addresses intermittent CI/E2E failures caused by duplicate ARO-HCP cluster name collisions when a retry occurs after a partial create failure (CS create succeeds but Cosmos write fails). It makes the frontend create path idempotent by reusing an existing Cluster Service cluster when Azure-identifying metadata matches, and deduplicates the same lookup logic already used by the backend controller into a shared internal/ocm helper.

Changes:

  • Add ocm.FindClusterByAzureInfo helper to locate an existing CS cluster by Azure metadata (and error on unexpected duplicates).
  • Update the frontend createHCPCluster() flow to “find-or-create” the CS cluster before proceeding with Cosmos writes, preventing duplicate-name collisions on retries.
  • Refactor the backend controller to use the shared helper and adjust tests accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
internal/ocm/client.go Adds shared FindClusterByAzureInfo helper for CS idempotency lookups by Azure metadata.
frontend/pkg/frontend/cluster.go Uses the shared lookup to reuse an existing CS cluster before attempting PostCluster.
backend/pkg/controllers/clustercreation/cluster_cluster_service_create_controller.go Replaces controller-local lookup logic with the shared ocm.FindClusterByAzureInfo.
backend/pkg/controllers/clustercreation/cluster_cluster_service_create_controller_test.go Updates tests to validate the shared helper behavior and new call site.

Comment thread frontend/pkg/frontend/cluster.go Outdated
Add test case "found among non-matching clusters" that verifies
FindClusterByAzureInfo correctly selects the matching cluster when CS
returns a mix of matching and non-matching clusters in the same
response. (swiencki review feedback)

Move the "creating resource" log line inside the create branch so it
only logs when actually creating, not when reusing an existing CS
cluster. (Copilot review feedback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 14, 2026 01:32

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@cssjr

Copy link
Copy Markdown
Collaborator Author

backend/pkg/controllers/clustercreation/cluster_cluster_service_create_controller_test.go - A second cluster in a different bucket asserting no manifest is created would prove the bucketing logic actually excludes foreign work.

Simon Wiencki (@swiencki) good call out. Added test "found among non-matching clusters" to address.

@cssjr

Copy link
Copy Markdown
Collaborator Author

/retest

@deads2k

Copy link
Copy Markdown
Collaborator

We are removing all cluster-service calls from the frontend. The PR doing so has tested green and is tagged for merge: #6121

/close

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

openshift-ci Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

David Eads (@deads2k): Closed this PR.

Details

In response to this:

We are removing all cluster-service calls from the frontend. The PR doing so has tested green and is tagged for merge: #6121

/close

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.

return utils.TrackError(err)
}
} else {
logger.Info("Reusing existing Cluster Service cluster found by Azure metadata", "csClusterHREF", resultingClusterServiceCluster.HREF())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we don't need this PR anymore, but I am curious what you think should happen if hte content in the existing cluster-service instance does not match the new create call.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If there wasn't a 100% match, it was going to create a new one (not reuse the old one). My understanding was a partial match was not duplicative so a new one could be created without conflict/idempotentcy issues.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants