Skip to content

OCPBUGS-98387: Add retry logic to fix Azure self-managed e2e presubmit failures#8983

Merged
openshift-merge-bot[bot] merged 2 commits into
openshift:mainfrom
bryan-cox:OCPBUGS-98387
Jul 11, 2026
Merged

OCPBUGS-98387: Add retry logic to fix Azure self-managed e2e presubmit failures#8983
openshift-merge-bot[bot] merged 2 commits into
openshift:mainfrom
bryan-cox:OCPBUGS-98387

Conversation

@bryan-cox

@bryan-cox bryan-cox commented Jul 10, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it:

The pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed presubmit is failing at 100% rate across all PRs, blocking all merges. Analysis of 8 failing runs across 6 different PRs identified three systemic root causes:

1. API server overload (25% of failures)

6 concurrent hypershift create cluster processes overwhelm the management cluster API server. validateClusterExistence makes a single client.Get with no retry — a transient timeout aborts the entire cluster creation.

Evidence from CI runs:

error validating cluster creation options: hostedcluster doesn't exist validation failed with error:
the server was unable to return a response in the time allotted, but may still be processing the request

Runs affected: 1927044143480832000, 1927037816306180096

Fix: Add retry.OnError with exponential backoff (5 steps, 1s base, 2x factor, ~31s max) for transient API errors (timeout, server timeout, internal error, too many requests, service unavailable) in validateClusterExistenceWithClient.

2. Azure 409 Conflict (25% of failures)

Concurrent public IP creation triggers ConflictingConcurrentWriteNotAllowed errors from Azure ARM. The Azure SDK's default retry policy retries 408/429/500/502/503/504 but not 409.

Evidence from CI runs:

failed to create public IP address, PUT https://management.azure.com/.../publicIPAddresses/...
RESPONSE 409: 409 Conflict
ERROR CODE: ConflictingConcurrentWriteNotAllowed

Runs affected: 1926697268705517568, 1927024697420627968

Fix: Add retry.OnError with exponential backoff (5 steps, 5s base, 2x factor, ~155s max) for 409 Conflict errors only in CreatePublicIPAddressForLB. BeginCreateOrUpdate is idempotent, so retries are safe.

3. Premature HostedCluster Available timeout (50% of failures)

The Available condition timeout (30m) is shorter than the overall test timeout (45m). Azure VM→node registration regularly takes just over 30 minutes when 6 clusters are created in parallel.

Evidence from CI runs:

ERROR: cluster <name> (<variant>) did not become Available:
timed out waiting for the condition

Nodes appear 1-3 minutes after timeout expiry in logs.

Runs affected: 1926627710921572352, 1926971610268430336

Fix: Align Available condition timeout from 30m to 45m to match waitTimeout already used for version rollout.

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-98387

Special notes for your reviewer:

  • The isTransientAPIError helper in create.go mirrors the pattern used in control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go:3410
  • The isAzureConflictError helper in networking.go is scoped to only 409 — the Azure SDK's default retry policy already handles 429/500/502/503/504
  • The timeout alignment in main.go makes the Available condition timeout match the existing waitTimeout (45m) already used for version rollout at line 352
  • validateClusterExistenceWithClient is in cmd/cluster/core/ (shared cross-platform path) — reviewed for safety across all platforms (AWS, Azure, KubeVirt, etc.); retry only activates on transient errors, no behavior change for non-transient paths

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

  • Reliability Improvements
    • Hosted-cluster creation now retries transient Kubernetes API failures when checking for pre-existing hosted-clusters.
    • Azure public IP provisioning now retries transient Azure conflicts during both initiation and completion.
  • Bug Fixes
    • Avoids retrying non-retryable failures and improves failure messaging when Azure public IP creation fails.
    • End-to-end HostedCluster availability wait time increased to 45 minutes.
  • Tests
    • Added coverage for hosted-cluster existence validation retries and Azure conflict/retry detection.

@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 jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 10, 2026
@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 10, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This pull request references Jira Issue OCPBUGS-98387, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

Summary

  • Add retry with exponential backoff to validateClusterExistence for transient API server errors (timeout, server timeout, too many requests, service unavailable)
  • Add retry with exponential backoff to CreatePublicIPAddressForLB for retryable Azure errors (409 ConflictingConcurrentWriteNotAllowed, 429, 500, 502, 503, 504)
  • Align HostedCluster Available condition timeout from 30m to 45m to match the overall wait timeout

Context

The pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed presubmit is failing at 100% rate across all PRs. Analysis of 8 failing runs across 6 different PRs identified three systemic root causes:

  1. API server overload (25% of failures): 6 concurrent hypershift create cluster processes overwhelm the management cluster API server. validateClusterExistence makes a single client.Get with no retry — a transient timeout aborts the entire cluster creation.

  2. Azure 409 conflicts (25% of failures): Concurrent public IP creation triggers ConflictingConcurrentWriteNotAllowed errors from Azure ARM. The Azure SDK's default retry policy does not retry 409s, and the code had no error-specific handling.

  3. Premature timeout (50% of failures): The Available condition timeout (30m) is shorter than the overall test timeout (45m). Azure VM→node registration regularly takes just over 30 minutes when 6 clusters are created in parallel.

Jira: https://issues.redhat.com/browse/OCPBUGS-98387

Test plan

  • New unit tests for validateClusterExistenceWithClient retry behavior (4 test cases)
  • New unit tests for isAzureRetryableError helper (9 test cases + errors.As verification)
  • make lint-fix passes (0 issues)
  • go vet passes on all modified packages
  • /pj-rehearse pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed to validate fix in CI

🤖 Generated with Claude Code

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

@openshift-ci

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

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: b1275aeb-521c-4f1b-902f-a2917fcb2cea

📥 Commits

Reviewing files that changed from the base of the PR and between 6016002 and 30054b3.

📒 Files selected for processing (3)
  • cmd/cluster/core/create.go
  • cmd/cluster/core/create_test.go
  • test/e2e/v2/cmd/create-guests/main.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/e2e/v2/cmd/create-guests/main.go
  • cmd/cluster/core/create.go
  • cmd/cluster/core/create_test.go

📝 Walkthrough

Walkthrough

The change adds retry-based HostedCluster existence validation for transient Kubernetes API errors. Azure public IP creation now retries HTTP 409 conflicts during creation and polling. Tests cover Kubernetes validation outcomes and Azure conflict classification. The end-to-end guest creation command increases the HostedCluster availability wait timeout from 30 to 45 minutes.

Sequence Diagram(s)

sequenceDiagram
  participant ClusterCreate
  participant ClusterValidation
  participant KubernetesAPI
  ClusterCreate->>ClusterValidation: validate HostedCluster existence
  ClusterValidation->>KubernetesAPI: Get HostedCluster
  KubernetesAPI-->>ClusterValidation: object, NotFound, or transient error
  ClusterValidation->>KubernetesAPI: retry transient error with backoff
  ClusterValidation-->>ClusterCreate: validation result
Loading
sequenceDiagram
  participant LoadBalancerSetup
  participant AzureSDK
  participant AzureAPI
  LoadBalancerSetup->>AzureSDK: BeginCreateOrUpdate
  AzureSDK->>AzureAPI: create public IP
  AzureAPI-->>AzureSDK: operation or HTTP 409 conflict
  LoadBalancerSetup->>AzureSDK: PollUntilDone
  AzureSDK->>AzureAPI: poll operation
  AzureAPI-->>LoadBalancerSetup: result or retryable error
  LoadBalancerSetup->>AzureSDK: retry conflict with backoff
Loading

Suggested reviewers: muraee

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding retry logic to address Azure self-managed e2e presubmit failures.
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 added test titles are static strings; no generated values, dates, IPs, namespaces, or runtime interpolation appear in any new names.
Test Structure And Quality ✅ Passed Added tests are isolated table-driven unit tests with fake clients; no cluster resources or indefinite waits, and the e2e wait uses an explicit cfg.waitTimeout.
Topology-Aware Scheduling Compatibility ✅ Passed No new topology-sensitive scheduling constraints were introduced; changes only add retry logic and extend an e2e availability timeout.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e spec was added; the only e2e change is a timeout bump in the harness, with no IPv4-only or public-internet assumptions.
No-Weak-Crypto ✅ Passed The PR only adds retry/timeouts and error-classification logic; no weak crypto, custom crypto, or secret comparison code was introduced.
Container-Privileges ✅ Passed Touched files are Go code/tests only; no container/K8s manifests were changed and no privileged/hostPID/hostNetwork/hostIPC/SYS_ADMIN/allowPrivilegeEscalation settings were found.
No-Sensitive-Data-In-Logs ✅ Passed No new log statements expose secrets/PII; added logs only include cluster names, namespaces, and provider error objects, not tokens or passwords.
✨ 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 Jul 10, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox

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 area/cli Indicates the PR includes changes for CLI approved Indicates a PR has been approved by an approver from all required OWNERS files. area/platform/azure PR/issue for Azure (AzurePlatform) platform area/testing Indicates the PR includes changes for e2e testing and removed do-not-merge/needs-area labels Jul 10, 2026

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

🧹 Nitpick comments (1)
cmd/infra/azure/networking.go (1)

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

Duplicate Azure error-classification logic.

isAzureRetryableError re-implements the same errors.As(*azcore.ResponseError) + status-code switch pattern already present in ClassifyAzureError (support/azureutil/errors.go), which also treats 409 as retryable. Consider extracting a shared "is this status code retryable" helper (or reusing ClassifyAzureError's classification) instead of maintaining two independent copies of the same Azure status-code logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/infra/azure/networking.go` around lines 288 - 303, Eliminate the
duplicated Azure retry-status classification in isAzureRetryableError by reusing
the existing classification logic from ClassifyAzureError or extracting a shared
helper for retryable HTTP status codes. Preserve the current handling of
azcore.ResponseError extraction and ensure status 409 and the existing transient
statuses remain retryable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/cluster/core/create_test.go`:
- Around line 1842-1855: The transientErrorClient currently only returns timeout
followed by NotFound, leaving validateClusterExistenceWithClient’s stale-error
path untested. Extend the fake with a configurable terminal error, or add a
separate fake, so it returns one or more transient timeouts followed by an
existing-cluster error; add a unit test asserting the final error from
validateClusterExistenceWithClient contains “already exists”.

In `@cmd/cluster/core/create.go`:
- Around line 813-835: Update validateClusterExistenceWithClient to base the
final error selection on retry.OnError’s returned err rather than stale lastErr:
preserve and return the “hostedcluster already exists” error from the final
attempt, while wrapping only when the retry operation itself ultimately fails
with a transient error. Alternatively, ensure the tracked error is updated on
every callback attempt so lastErr cannot override the final result.

In `@cmd/infra/azure/networking.go`:
- Around line 288-303: Limit isAzureRetryableError to return true only for
http.StatusConflict (409); remove the other status codes because the Azure
client’s default retry policy already handles them. Update the surrounding
BeginCreateOrUpdate/PollUntilDone retry usage if needed so the outer retry
wrapper exclusively handles 409 Conflict.

---

Nitpick comments:
In `@cmd/infra/azure/networking.go`:
- Around line 288-303: Eliminate the duplicated Azure retry-status
classification in isAzureRetryableError by reusing the existing classification
logic from ClassifyAzureError or extracting a shared helper for retryable HTTP
status codes. Preserve the current handling of azcore.ResponseError extraction
and ensure status 409 and the existing transient statuses remain retryable.
🪄 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: 3848c13f-9374-485b-84f7-c7515be14856

📥 Commits

Reviewing files that changed from the base of the PR and between e708f6f and c238ce3.

📒 Files selected for processing (5)
  • cmd/cluster/core/create.go
  • cmd/cluster/core/create_test.go
  • cmd/infra/azure/networking.go
  • cmd/infra/azure/networking_test.go
  • test/e2e/v2/cmd/create-guests/main.go

Comment thread cmd/cluster/core/create_test.go
Comment thread cmd/cluster/core/create.go Outdated
Comment thread cmd/infra/azure/networking.go Outdated
@bryan-cox

Copy link
Copy Markdown
Member Author

/jira refresh

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.61111% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.69%. Comparing base (e708f6f) to head (30054b3).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
cmd/infra/azure/networking.go 20.45% 35 Missing ⚠️
cmd/cluster/core/create.go 92.85% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8983      +/-   ##
==========================================
+ Coverage   43.67%   43.69%   +0.01%     
==========================================
  Files         771      771              
  Lines       95840    95895      +55     
==========================================
+ Hits        41862    41897      +35     
- Misses      51067    51087      +20     
  Partials     2911     2911              
Files with missing lines Coverage Δ
cmd/cluster/core/create.go 63.00% <92.85%> (+1.25%) ⬆️
cmd/infra/azure/networking.go 32.09% <20.45%> (-0.44%) ⬇️
Flag Coverage Δ
cmd-support 37.27% <48.61%> (+0.04%) ⬆️
cpo-hostedcontrolplane 45.91% <ø> (ø)
cpo-other 45.11% <ø> (ø)
hypershift-operator 53.91% <ø> (ø)
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.

@openshift-ci-robot openshift-ci-robot added jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. and removed jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Jul 10, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: This pull request references Jira Issue OCPBUGS-98387, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

/jira refresh

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

Copy link
Copy Markdown

@bryan-cox: This pull request references Jira Issue OCPBUGS-98387, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (5.0.0) matches configured target version for branch (5.0.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)
Details

In response to this:

What this PR does / why we need it:

The pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed presubmit is failing at 100% rate across all PRs, blocking all merges. Analysis of 8 failing runs across 6 different PRs identified three systemic root causes:

1. API server overload (25% of failures)

6 concurrent hypershift create cluster processes overwhelm the management cluster API server. validateClusterExistence makes a single client.Get with no retry — a transient timeout aborts the entire cluster creation.

Evidence from CI runs:

error validating cluster creation options: hostedcluster doesn't exist validation failed with error:
the server was unable to return a response in the time allotted, but may still be processing the request

Runs affected: 1927044143480832000, 1927037816306180096

Fix: Add retry.OnError with exponential backoff (5 steps, 1s base, 2x factor, ~31s max) for transient API errors (timeout, server timeout, internal error, too many requests, service unavailable) in validateClusterExistenceWithClient.

2. Azure 409 Conflict (25% of failures)

Concurrent public IP creation triggers ConflictingConcurrentWriteNotAllowed errors from Azure ARM. The Azure SDK's default retry policy retries 408/429/500/502/503/504 but not 409.

Evidence from CI runs:

failed to create public IP address, PUT https://management.azure.com/.../publicIPAddresses/...
RESPONSE 409: 409 Conflict
ERROR CODE: ConflictingConcurrentWriteNotAllowed

Runs affected: 1926697268705517568, 1927024697420627968

Fix: Add retry.OnError with exponential backoff (5 steps, 5s base, 2x factor, ~155s max) for 409 Conflict errors only in CreatePublicIPAddressForLB. BeginCreateOrUpdate is idempotent, so retries are safe.

3. Premature HostedCluster Available timeout (50% of failures)

The Available condition timeout (30m) is shorter than the overall test timeout (45m). Azure VM→node registration regularly takes just over 30 minutes when 6 clusters are created in parallel.

Evidence from CI runs:

ERROR: cluster <name> (<variant>) did not become Available:
timed out waiting for the condition

Nodes appear 1-3 minutes after timeout expiry in logs.

Runs affected: 1926627710921572352, 1926971610268430336

Fix: Align Available condition timeout from 30m to 45m to match waitTimeout already used for version rollout.

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-98387

Special notes for your reviewer:

  • The isTransientAPIError helper in create.go mirrors the pattern used in control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go:3410
  • The isAzureConflictError helper in networking.go is scoped to only 409 — the Azure SDK's default retry policy already handles 429/500/502/503/504
  • The timeout alignment in main.go makes the Available condition timeout match the existing waitTimeout (45m) already used for version rollout at line 352
  • validateClusterExistenceWithClient is in cmd/cluster/core/ (shared cross-platform path) — reviewed for safety across all platforms (AWS, Azure, KubeVirt, etc.); retry only activates on transient errors, no behavior change for non-transient paths

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
cmd/infra/azure/networking.go (1)

315-350: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the retry loop context-aware. retry.OnError doesn’t take a context.Context, so a canceled request can still sit in the backoff sleep before the SDK sees ctx.Done(). Switch this to a context-aware backoff loop and keep returning the last conflict error when retries are exhausted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/infra/azure/networking.go` around lines 315 - 350, Replace retry.OnError
in the public IP creation flow with a context-aware retry loop using the
existing backoff configuration, checking ctx.Done() before and during each delay
so cancellation exits promptly. Preserve the BeginCreateOrUpdate and
PollUntilDone retry behavior for isAzureConflictError, and return the final
conflict error when all retries are exhausted; update the logic around result
assignment accordingly.

Source: Path instructions

🧹 Nitpick comments (1)
cmd/infra/azure/networking_test.go (1)

237-246: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a stable “When ... it should ...” subtest description.

Wrap this assertion in a descriptive t.Run, e.g. When an Azure response error is wrapped it should be discoverable, to follow the repository’s unit-test convention.

As per coding guidelines, “Always use ‘When ... it should ...’ format for describing test cases when creating unit tests.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/infra/azure/networking_test.go` around lines 237 - 246, Wrap the
assertions in TestErrorsAsAzureResponseError with a descriptive t.Run subtest
named “When an Azure response error is wrapped it should be discoverable”,
keeping the existing error setup and expectations inside the subtest.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/cluster/core/create_test.go`:
- Around line 1823-1831: Update the “forbidden error” test to verify the client
performs exactly one GET, not merely returns the expected error. Add call
tracking to nonTransientErrorClient, expose or inspect its GET count after
execution, and assert it equals one while retaining the existing error
assertion.

---

Outside diff comments:
In `@cmd/infra/azure/networking.go`:
- Around line 315-350: Replace retry.OnError in the public IP creation flow with
a context-aware retry loop using the existing backoff configuration, checking
ctx.Done() before and during each delay so cancellation exits promptly. Preserve
the BeginCreateOrUpdate and PollUntilDone retry behavior for
isAzureConflictError, and return the final conflict error when all retries are
exhausted; update the logic around result assignment accordingly.

---

Nitpick comments:
In `@cmd/infra/azure/networking_test.go`:
- Around line 237-246: Wrap the assertions in TestErrorsAsAzureResponseError
with a descriptive t.Run subtest named “When an Azure response error is wrapped
it should be discoverable”, keeping the existing error setup and expectations
inside the subtest.
🪄 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: b10ae2b9-3cc4-4088-b993-d170c1a14097

📥 Commits

Reviewing files that changed from the base of the PR and between c238ce3 and a8b9cdd.

📒 Files selected for processing (4)
  • cmd/cluster/core/create.go
  • cmd/cluster/core/create_test.go
  • cmd/infra/azure/networking.go
  • cmd/infra/azure/networking_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/cluster/core/create.go

Comment thread cmd/cluster/core/create_test.go
…ter creation

- Add validateClusterExistenceWithClient with exponential backoff retry
  (5 steps, 1s base, 2x factor, ~31s max) for transient API errors
  (timeout, server timeout, internal error, too many requests, service
  unavailable) in the shared CLI create path
- Add isAzureConflictError and retry wrapper around public IP
  BeginCreateOrUpdate/PollUntilDone for Azure 409 Conflict errors
  (5 steps, 5s base, 2x factor, ~155s max); scoped to only 409 since
  the Azure SDK default retry policy already handles 408/429/500-504
- Add 7 unit tests for validateClusterExistence covering success,
  already-exists, transient retry+succeed, exhaustion, immediate
  non-transient fail, service unavailable retry, and timeout-then-exists
- Add 9 unit tests for isAzureConflictError covering 409 variants,
  non-retryable codes, non-Azure errors, wrapped errors, and nil

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bryan-cox
bryan-cox marked this pull request as ready for review July 10, 2026 12:59
@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 10, 2026
@openshift-ci
openshift-ci Bot requested review from enxebre and muraee July 10, 2026 13:01

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

@bryan-cox many many thanks for this!
Only nits, and in general I think will improve our pass rate.
I don't think there will be any impact on other platforms.
May you have a look at my comments and follow-up or pushing back?
TY!

Comment thread cmd/cluster/core/create_test.go Outdated
type timeoutThenExistsClient struct {
crclient.Client
callsBeforeTerminal int
existingCluster *hyperv1.HostedCluster

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.

Is existingCluster used anywhere?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — the field isn't used since the production code only checks err == nil to determine the cluster exists. Removed it.


AI-assisted response via Claude Code

if err := c.Get(ctx, crclient.ObjectKeyFromObject(cluster), cluster); err == nil {
return fmt.Errorf("hostedcluster %s already exists", crclient.ObjectKeyFromObject(cluster))
} else if !apierrors.IsNotFound(err) {
return err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

May we log something before returning err? Guess it may add some info for next failures/flakyness

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done. Added opts.Log.Error(...) before the return to log the error with namespace/name context for easier debugging of future flakiness.


AI-assisted response via Claude Code

Comment thread test/e2e/v2/cmd/create-guests/main.go Outdated
// Phase 3: Watch for Available condition on all clusters.
log.Println("Phase 3: Waiting for all clusters to become Available")
availableErrors := waitForClustersAvailable(ctx, mgmtClient, cfg.namespace, named, 30*time.Minute)
availableErrors := waitForClustersAvailable(ctx, mgmtClient, cfg.namespace, named, 45*time.Minute)

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.

Claude is reporting this timeout now is similar to timeout at main.go:122. Are they related, don't think so, May you clarifiy with a comment evenutally?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

They are related — both are timeouts for cluster readiness but for different conditions. cfg.waitTimeout (line 122, 45m) is used for version rollout at line 352, and this timeout was for the Available condition (previously 30m). Changed this to use cfg.waitTimeout directly instead of a magic number, which makes the relationship explicit and keeps them in sync.


AI-assisted response via Claude Code

…Timeout

Increase the HostedCluster Available condition timeout from 30m to 45m
to match the overall waitTimeout already used for version rollout. Azure
VM-to-node registration regularly exceeds 30 minutes when 6 clusters
are created in parallel, causing premature timeouts in the self-managed
e2e presubmit.

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

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 10, 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-4-22
/test e2e-aws-4-22
/test e2e-aks
/test e2e-aws
/test e2e-aws-upgrade-hypershift-operator
/test e2e-azure-v2-self-managed
/test e2e-kubevirt-aws-ovn-reduced
/test e2e-v2-aws
/test e2e-v2-gke

@bryan-cox

Copy link
Copy Markdown
Member Author

/verified by e2e

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

Copy link
Copy Markdown

@bryan-cox: This PR has been marked as verified by e2e.

Details

In response to this:

/verified by e2e

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.

@bryan-cox

Copy link
Copy Markdown
Member Author

/retest

1 similar comment
@bryan-cox

Copy link
Copy Markdown
Member Author

/retest

@cwbotbot

cwbotbot commented Jul 10, 2026

Copy link
Copy Markdown

Test Results

e2e-aws

e2e-aks

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD ce1766a and 2 for PR HEAD 30054b3 in total

@bryan-cox

Copy link
Copy Markdown
Member Author

/retest

@bryan-cox

Copy link
Copy Markdown
Member Author

/test okd-scos-images

@hypershift-jira-solve-ci

Copy link
Copy Markdown
Contributor

Now I have the complete picture. The PR modifies Azure networking retry logic and test files — it has nothing to do with the Dockerfile or build process. The build failure is an OOM kill during Go compilation in CI. Let me calculate the timing to confirm.

The build hypershift-amd64 ran for 4h58m28s (started ~05:29, failed ~10:27) — an extremely long duration for a Go build that should take minutes. The Go compiler was killed by the OOM killer (signal: killed, exit code 137) while compiling the massive github.com/aws/aws-sdk-go-v2/service/ec2/types package. This is a CI infrastructure issue — the build pod ran out of memory.

Test Failure Analysis Complete

Job Information

  • Prow Job: pull-ci-openshift-hypershift-main-okd-scos-images
  • Build ID: 2075804740536504320
  • Target: [images] (variant: okd-scos)
  • PR: #8983 — OCPBUGS-98387: Add retry logic to fix Azure self-managed e2e presubmit failures
  • Cluster: build01
  • Duration: 5h37m18s (started 04:49 UTC, completed 10:27 UTC)

Test Failure Analysis

Error

github.com/aws/aws-sdk-go-v2/service/ec2/types: /usr/lib/golang/pkg/tool/linux_amd64/compile: signal: killed
/usr/bin/go: line 19:    63 Killed                  GO_COMPLIANCE_INFO=0 GO_COMPLIANCE_DEBUG=0 go.real "${ARGS[@]}"
make: *** [Makefile:217: control-plane-operator] Error 137
error: build error: building at STEP "RUN make control-plane-operator && make control-plane-pki-operator": while running runtime: exit status 2

Summary

The okd-scos-images job failed due to an Out-Of-Memory (OOM) kill during the Go compilation step of the Dockerfile.control-plane image build. The Go compiler process was killed by the Linux OOM killer (signal 9 / exit code 137) while compiling github.com/aws/aws-sdk-go-v2/service/ec2/types — a notoriously large, memory-intensive package. The image build ran for nearly 5 hours before being killed, indicating severe memory pressure and likely swap thrashing on the build node. This failure is a CI infrastructure issue completely unrelated to the PR's changes (which modify Azure networking retry logic in Go source files).

Root Cause

The Go compiler process was OOM-killed by the Linux kernel during the make control-plane-operator step inside the Dockerfile.control-plane build. The key evidence chain:

  1. Exit code 137 — In Unix, exit code 137 = 128 + 9 (SIGKILL). The process was forcibly killed by the kernel's OOM killer, not by a timeout or user action.

  2. signal: killed on /usr/lib/golang/pkg/tool/linux_amd64/compile — The specific Go toolchain binary (compile) was killed while processing the github.com/aws/aws-sdk-go-v2/service/ec2/types package. This package is one of the largest in the AWS SDK (it contains thousands of generated type definitions for EC2 API) and is a known memory-intensive compilation target.

  3. Build duration of ~5 hours — The hypershift-amd64 build started at 05:29 UTC and failed at 10:27 UTC (~4h58m). A normal Go build of this project should complete in minutes. This extreme duration suggests the build pod was heavily memory-constrained, causing the Go compiler to swap-thrash before ultimately being OOM-killed.

  4. Build flags -gcflags=all='-N -l' — The build uses debug flags that disable optimizations (-N) and inlining (-l), which increases memory usage during compilation.

  5. The PR's changes are irrelevant — PR OCPBUGS-98387: Add retry logic to fix Azure self-managed e2e presubmit failures #8983 modifies 5 files (cmd/cluster/core/create.go, cmd/infra/azure/networking.go, and their tests, plus test/e2e/v2/cmd/create-guests/main.go). None of these changes affect the Dockerfile, Makefile, build configuration, or Go module dependencies. The OOM condition is a CI infrastructure issue.

This is a CI infrastructure flake — the build pod on build01 did not have sufficient memory to compile the hypershift control-plane-operator binary. A /retest should resolve the issue if the pod lands on a node with more available memory.

Recommendations
  1. Retest the job — Run /test okd-scos-images to retry. This is an infrastructure flake and should pass on retry when the build pod lands on a node with sufficient available memory.

  2. No code changes needed — The PR's changes (Azure networking retry logic) are completely unrelated to this build failure. The same Dockerfile and Makefile are used by all PRs to this repo.

  3. If the failure recurs repeatedly, consider:

    • Filing a CI infrastructure issue to increase memory limits for image builds in the okd-scos variant
    • Investigating whether the okd-scos build configuration has lower resource limits than the standard OCP variant
    • The -gcflags=all='-N -l' debug flags could be removed for CI image builds to reduce compiler memory usage
Evidence
Evidence Detail
Kill signal signal: killed on Go compile binary — Linux OOM killer (SIGKILL)
Exit code 137 (128 + SIGKILL=9) — confirms kernel OOM kill, not timeout
Killed package github.com/aws/aws-sdk-go-v2/service/ec2/types — one of the largest AWS SDK packages
Build duration ~4h58m for a build that normally takes minutes — indicates memory thrashing before OOM
Build step [1/2] STEP 6/6: RUN make control-plane-operator && make control-plane-pki-operator
Build reason DockerBuildFailed: Dockerfile build strategy has failed
CI cluster build01
PR relevance PR modifies 5 Go files (Azure retry logic + tests) — no build/Dockerfile/dependency changes
Sparse checkout Job uses sparse_checkout_files: ["Dockerfile.control-plane"] — confirms this is an image-build-only job

@bryan-cox

Copy link
Copy Markdown
Member Author

/retest

@openshift-ci

openshift-ci Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

@bryan-cox: 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 8ea96d7 into openshift:main Jul 11, 2026
41 checks passed
@openshift-ci-robot

Copy link
Copy Markdown

@bryan-cox: Jira Issue Verification Checks: Jira Issue OCPBUGS-98387
✔️ This pull request was pre-merge verified.
✔️ All associated pull requests have merged.
✔️ All associated, merged pull requests were pre-merge verified.

Jira Issue OCPBUGS-98387 has been moved to the MODIFIED state and will move to the VERIFIED state when the change is available in an accepted nightly payload. 🕓

Details

In response to this:

What this PR does / why we need it:

The pull-ci-openshift-hypershift-main-e2e-azure-v2-self-managed presubmit is failing at 100% rate across all PRs, blocking all merges. Analysis of 8 failing runs across 6 different PRs identified three systemic root causes:

1. API server overload (25% of failures)

6 concurrent hypershift create cluster processes overwhelm the management cluster API server. validateClusterExistence makes a single client.Get with no retry — a transient timeout aborts the entire cluster creation.

Evidence from CI runs:

error validating cluster creation options: hostedcluster doesn't exist validation failed with error:
the server was unable to return a response in the time allotted, but may still be processing the request

Runs affected: 1927044143480832000, 1927037816306180096

Fix: Add retry.OnError with exponential backoff (5 steps, 1s base, 2x factor, ~31s max) for transient API errors (timeout, server timeout, internal error, too many requests, service unavailable) in validateClusterExistenceWithClient.

2. Azure 409 Conflict (25% of failures)

Concurrent public IP creation triggers ConflictingConcurrentWriteNotAllowed errors from Azure ARM. The Azure SDK's default retry policy retries 408/429/500/502/503/504 but not 409.

Evidence from CI runs:

failed to create public IP address, PUT https://management.azure.com/.../publicIPAddresses/...
RESPONSE 409: 409 Conflict
ERROR CODE: ConflictingConcurrentWriteNotAllowed

Runs affected: 1926697268705517568, 1927024697420627968

Fix: Add retry.OnError with exponential backoff (5 steps, 5s base, 2x factor, ~155s max) for 409 Conflict errors only in CreatePublicIPAddressForLB. BeginCreateOrUpdate is idempotent, so retries are safe.

3. Premature HostedCluster Available timeout (50% of failures)

The Available condition timeout (30m) is shorter than the overall test timeout (45m). Azure VM→node registration regularly takes just over 30 minutes when 6 clusters are created in parallel.

Evidence from CI runs:

ERROR: cluster <name> (<variant>) did not become Available:
timed out waiting for the condition

Nodes appear 1-3 minutes after timeout expiry in logs.

Runs affected: 1926627710921572352, 1926971610268430336

Fix: Align Available condition timeout from 30m to 45m to match waitTimeout already used for version rollout.

Which issue(s) this PR fixes:

Fixes https://issues.redhat.com/browse/OCPBUGS-98387

Special notes for your reviewer:

  • The isTransientAPIError helper in create.go mirrors the pattern used in control-plane-operator/hostedclusterconfigoperator/controllers/resources/resources.go:3410
  • The isAzureConflictError helper in networking.go is scoped to only 409 — the Azure SDK's default retry policy already handles 429/500/502/503/504
  • The timeout alignment in main.go makes the Available condition timeout match the existing waitTimeout (45m) already used for version rollout at line 352
  • validateClusterExistenceWithClient is in cmd/cluster/core/ (shared cross-platform path) — reviewed for safety across all platforms (AWS, Azure, KubeVirt, etc.); retry only activates on transient errors, no behavior change for non-transient paths

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

  • Reliability Improvements
  • Hosted-cluster creation now retries transient Kubernetes API failures when checking for pre-existing hosted-clusters.
  • Azure public IP provisioning now retries transient Azure conflicts during both initiation and completion.
  • Bug Fixes
  • Avoids retrying non-retryable failures and improves failure messaging when Azure public IP creation fails.
  • End-to-end HostedCluster availability wait time increased to 45 minutes.
  • Tests
  • Added coverage for hosted-cluster existence validation retries and Azure conflict/retry detection.

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-robot

Copy link
Copy Markdown
Contributor

Fix included in release 5.0.0-0.nightly-2026-07-11-232605

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/cli Indicates the PR includes changes for CLI area/platform/azure PR/issue for Azure (AzurePlatform) platform area/testing Indicates the PR includes changes for e2e testing jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. 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. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants