Skip to content

OCPBUGS-99883: fix(azure): cache VNet location validation and requeue on ARM 429 throttling - #9123

Open
miquelsi wants to merge 1 commit into
openshift:mainfrom
miquelsi:cache-vnet-validation
Open

OCPBUGS-99883: fix(azure): cache VNet location validation and requeue on ARM 429 throttling#9123
miquelsi wants to merge 1 commit into
openshift:mainfrom
miquelsi:cache-vnet-validation

Conversation

@miquelsi

@miquelsi miquelsi commented Jul 27, 2026

Copy link
Copy Markdown

What this PR does / why we need it:

Skip Azure ARM VNet/NSG/RG location checks once ValidHostedControlPlaneConfiguration is True for the current generation, and requeue with Retry-After on Azure API errors instead of hard-gating reconciliation during throttling.

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/OCPBUGS-99883

Special notes for your reviewer:

Checklist:

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

Summary by CodeRabbit

  • Bug Fixes
    • Improved hosted control plane recovery from temporary Azure API errors.
    • Reconciliation now honors Azure retry timing and uses appropriate fallback delays for rate limiting and authorization responses.
    • Avoided repeating Azure resource group location validation when the configuration is already confirmed valid.

…on ARM 429 throttling

Skip Azure ARM VNet/NSG/RG location checks once ValidHostedControlPlaneConfiguration
is True for the current generation, and requeue with Retry-After on Azure API errors
instead of hard-gating reconciliation during throttling.

Signed-off-by: Miquel Simon <msimonma@redhat.com>
Assisted-by: Cursor Grok 4.5 (via Cursor)
Co-authored-by: Cursor <cursoragent@cursor.com>
@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

openshift-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: miquelsi
Once this PR has been reviewed and has the lgtm label, please assign cblecker 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 added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Jul 27, 2026
@openshift-ci

openshift-ci Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Hi @miquelsi. Thanks for your PR.

I'm waiting for a openshift member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

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.

@openshift-ci openshift-ci Bot added the area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release label Jul 27, 2026
@openshift-ci
openshift-ci Bot requested review from bryan-cox and enxebre July 27, 2026 13:33
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Hosted control plane reconciliation now retains configuration validation errors for control-flow decisions. Transient Azure API errors can trigger timed requeues instead of permanent blocking, with retry intervals derived from Retry-After or status-specific defaults. Azure resource group location validation is skipped when the configuration condition is current for the HCP generation. New table-driven tests cover condition freshness and Azure 429, 403, and non-retryable error handling.

Sequence Diagram(s)

sequenceDiagram
  participant HostedControlPlaneReconciler
  participant AzureARM
  participant ControllerQueue
  HostedControlPlaneReconciler->>AzureARM: validate configuration and locations
  AzureARM-->>HostedControlPlaneReconciler: validation result or ResponseError
  HostedControlPlaneReconciler->>ControllerQueue: requeue after Retry-After or default interval
Loading

Possibly related issues

Suggested reviewers: bryan-cox, csrwng

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed Added test titles are static and deterministic; they contain no generated names, timestamps, UUIDs, or other run-specific values.
Test Structure And Quality ✅ Passed The added tests are isolated table-driven unit tests with no cluster resources, waits, or cleanup concerns, and they follow local test patterns.
Topology-Aware Scheduling Compatibility ✅ Passed No scheduling constraints were added: the PR only changes Azure validation/requeue logic and tests; no replicas, affinity, nodeSelectors, PDBs, or topology checks were introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Only unit tests were added; the diff contains no It/Describe/Context/When e2e tests and no IPv4 or external-network assumptions in the new tests.
No-Weak-Crypto ✅ Passed No weak crypto, custom crypto, or insecure secret comparisons were added; the change only introduces Azure validation/requeue helpers and tests.
Container-Privileges ✅ Passed The PR only changes Go controller/test files; no container/K8s manifests or privileged/hostNetwork/IPC/SYS_ADMIN/allowPrivilegeEscalation settings were added.
No-Sensitive-Data-In-Logs ✅ Passed New logs only mention Azure requeue timing and credential reuse; no passwords, tokens, PII, hostnames, or customer data are logged in the changed paths.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main changes: caching Azure location validation and requeuing on ARM throttling errors.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go (1)

623-627: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid shadowing the reconciler error variable.

Line 623 shadows the err declared at Line 590. Assign the validation result to configValidationErr before branching instead.

Proposed fix
-		if err := r.validateConfigAndClusterCapabilities(ctx, hostedControlPlane); err != nil {
-			configValidationErr = err
+		configValidationErr = r.validateConfigAndClusterCapabilities(ctx, hostedControlPlane)
+		if configValidationErr != nil {
 			condition.Status = metav1.ConditionFalse
-			condition.Message = err.Error()
+			condition.Message = configValidationErr.Error()

As per coding guidelines, “Avoid variable shadowing.”

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go`
around lines 623 - 627, Update the validation handling in the reconciler to
assign the result of validateConfigAndClusterCapabilities to configValidationErr
before checking whether it is non-nil, rather than declaring a shadowing err
inside the if initializer. Preserve the existing condition status, message, and
reason updates when configValidationErr contains an error.

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
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go`:
- Around line 3252-3258: Update requeueOnAzureAPIError to use both results from
hyperazureutil.ClassifyAzureError, returning the classifier’s non-requeueable
outcome instead of always requeueing every azcore.ResponseError. Preserve the
existing behavior for retryable responses, and add coverage for a permanent
Azure response such as http.StatusBadRequest.

---

Nitpick comments:
In
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go`:
- Around line 623-627: Update the validation handling in the reconciler to
assign the result of validateConfigAndClusterCapabilities to configValidationErr
before checking whether it is non-nil, rather than declaring a shadowing err
inside the if initializer. Preserve the existing condition status, message, and
reason updates when configValidationErr contains an error.
🪄 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: Pro Plus

Run ID: 22af151c-8f78-4845-8550-85c7e439ebae

📥 Commits

Reviewing files that changed from the base of the PR and between 02c0659 and 0d04b8a.

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

Comment on lines +3252 to +3258
func requeueOnAzureAPIError(err error) (ctrl.Result, bool) {
var respErr *azcore.ResponseError
if err == nil || !errors.As(err, &respErr) {
return ctrl.Result{}, false
}
requeueAfter, _ := hyperazureutil.ClassifyAzureError(err)
return ctrl.Result{RequeueAfter: requeueAfter}, true

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -A35 -B5 'func\s+ClassifyAzureError\b' .
rg -n -A20 -B5 'TestRequeueOnAzureAPIError|StatusBadRequest' control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go

Repository: openshift/hypershift

Length of output: 3161


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== Locate relevant symbols and file sizes =="
wc -l control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go \
      control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go \
      support/azureutil/errors.go

echo
echo "== Requeue helper and callers =="
rg -n -A25 -B8 'func requeueOnAzureAPIError|requeueOnAzureAPIError\(' control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go

echo
echo "== Azure test cases around requeue helpers =="
sed -n '4708,4815p' control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go

echo
echo "== Azure error classifier =="
sed -n '1,140p' support/azureutil/errors.go

echo
echo "== StatusBadRequest references in controller tests =="
rg -n -C4 '400|Bad Request|BadRequest|StatusBadRequest|Validation|invalid' control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go

Repository: openshift/hypershift

Length of output: 10081


Requeue only retryable Azure API responses.

requeueOnAzureAPIError currently requeues every *azcore.ResponseError, including permanent validation errors and any HTTP 400 response from Azure. Return the classifier’s non-requeueable outcome in requeueOnAzureAPIError and add a coverage case for a permanent Azure status such as http.StatusBadRequest.

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go`
around lines 3252 - 3258, Update requeueOnAzureAPIError to use both results from
hyperazureutil.ClassifyAzureError, returning the classifier’s non-requeueable
outcome instead of always requeueing every azcore.ResponseError. Preserve the
existing behavior for retryable responses, and add coverage for a permanent
Azure response such as http.StatusBadRequest.

@bryan-cox

Copy link
Copy Markdown
Member

/retitle OCPBUGS-99883: fix(azure): cache VNet location validation and requeue on ARM 429 throttling

@openshift-ci openshift-ci Bot changed the title fix(azure): cache VNet location validation and requeue on ARM 429 throttling OCPBUGS-99883: fix(azure): cache VNet location validation and requeue on ARM 429 throttling Jul 28, 2026
@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 28, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@miquelsi: This pull request references Jira Issue OCPBUGS-99883, which is invalid:

  • expected the bug to target the "5.0.0" version, but no target version was set
  • expected the bug to be in one of the following states: NEW, ASSIGNED, POST, but it is ON_QA instead

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:

What this PR does / why we need it:

Skip Azure ARM VNet/NSG/RG location checks once ValidHostedControlPlaneConfiguration is True for the current generation, and requeue with Retry-After on Azure API errors instead of hard-gating reconciliation during throttling.

Which issue(s) this PR fixes:

Fixes https://redhat.atlassian.net/browse/OCPBUGS-99883

Special notes for your reviewer:

Checklist:

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

Summary by CodeRabbit

  • Bug Fixes
  • Improved hosted control plane recovery from temporary Azure API errors.
  • Reconciliation now honors Azure retry timing and uses appropriate fallback delays for rate limiting and authorization responses.
  • Avoided repeating Azure resource group location validation when the configuration is already confirmed valid.

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

Review from hypershift-staff-engineer agent — two-axis review (standards + spec compliance) against OCPBUGS-99883.

Comment on lines +3252 to +3259
func requeueOnAzureAPIError(err error) (ctrl.Result, bool) {
var respErr *azcore.ResponseError
if err == nil || !errors.As(err, &respErr) {
return ctrl.Result{}, false
}
requeueAfter, _ := hyperazureutil.ClassifyAzureError(err)
return ctrl.Result{RequeueAfter: requeueAfter}, true
}

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 spec asks for 429 handling specifically, but this requeues on any azcore.ResponseError — including 403 (permissions misconfigured). A 403 on VNet validation is a genuine configuration error that should hard-gate reconciliation so the user sees it immediately, not silently retry for 10 minutes.

Consider filtering to only transient codes:

if respErr.StatusCode != http.StatusTooManyRequests && (respErr.StatusCode < 500 || respErr.StatusCode >= 600) {
    return ctrl.Result{}, false
}

return ctrl.Result{}, nil
}

var configValidationErr error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This variable is declared here but consumed ~100 lines later at the gate check (line 714). The reconciler uses bare {} blocks to isolate scope, and this threads state across those boundaries. Could this be scoped tighter — e.g., by checking requeueOnAzureAPIError inline where the condition is set to False, or by attaching the error to the condition's Message and re-parsing it at the gate?

Comment on lines +3237 to +3259
// shouldSkipAzureResourceGroupLocationValidation reports whether Azure ARM calls to
// verify VNet/NSG/RG locations can be skipped because validation already succeeded for
// the current generation. Locations are immutable for a given set of Azure resource IDs,
// so re-checking on every reconcile is unnecessary and contributes to ARM throttling.
func shouldSkipAzureResourceGroupLocationValidation(hcp *hyperv1.HostedControlPlane) bool {
validConfig := meta.FindStatusCondition(hcp.Status.Conditions, string(hyperv1.ValidHostedControlPlaneConfiguration))
return validConfig != nil &&
validConfig.Status == metav1.ConditionTrue &&
validConfig.ObservedGeneration == hcp.Generation
}

// requeueOnAzureAPIError returns a RequeueAfter result when err wraps an Azure API
// ResponseError (e.g. 429), using ClassifyAzureError for differentiated backoff that
// respects the Retry-After header. Permanent validation failures that are not Azure
// API errors return false so reconciliation remains hard-gated.
func requeueOnAzureAPIError(err error) (ctrl.Result, bool) {
var respErr *azcore.ResponseError
if err == nil || !errors.As(err, &respErr) {
return ctrl.Result{}, false
}
requeueAfter, _ := hyperazureutil.ClassifyAzureError(err)
return ctrl.Result{RequeueAfter: requeueAfter}, true
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: per DEVELOPMENT.md ("Platform-specific logic should be isolated"), these Azure-specific helpers would fit better in support/azureutil/ alongside ClassifyAzureError. shouldSkipAzureResourceGroupLocationValidation only reads conditions and generation — it's Azure-specific by name/intent, not by implementation, so moving it keeps the ~3250-line reconciler platform-neutral.

Comment on lines +3241 to +3246
func shouldSkipAzureResourceGroupLocationValidation(hcp *hyperv1.HostedControlPlane) bool {
validConfig := meta.FindStatusCondition(hcp.Status.Conditions, string(hyperv1.ValidHostedControlPlaneConfiguration))
return validConfig != nil &&
validConfig.Status == metav1.ConditionTrue &&
validConfig.ObservedGeneration == hcp.Generation
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor: the spec suggests caching based on specific field changes (VnetID, SubnetID, NetworkSecurityGroupID), but the implementation uses ObservedGeneration == hcp.Generation. This means any unrelated spec change (e.g., authentication config) invalidates the cache and triggers another ARM call. The generation check is safer and simpler — just flagging the trade-off since the spec called out field-level granularity for the 400-cluster scenario.

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

Labels

area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants