Skip to content

OCPBUGS-86690: fix Azure cluster deletion hanging when resource groups are already deleted - #8682

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
vsolanki12:OCPBUGS-86690-azure-orphaned-machine-deletion
Sep 3, 2026
Merged

OCPBUGS-86690: fix Azure cluster deletion hanging when resource groups are already deleted#8682
openshift-merge-bot[bot] merged 1 commit into
openshift:mainfrom
vsolanki12:OCPBUGS-86690-azure-orphaned-machine-deletion

Conversation

@vsolanki12

@vsolanki12 vsolanki12 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

What this PR does / why we need it:

When an Azure self-managed HCP cluster's resource groups are deleted out-of-band (e.g. via Azure portal or expired credentials), hypershift destroy cluster azure hangs indefinitely. AzureMachines get stuck in deletion because CAPZ cannot communicate with the deleted Azure infrastructure, and their finalizers are never cleared.

This PR uses the same DeletionFailed condition approach as #8296 (ARO HCP) but extends coverage to self-managed Azure clusters. PR #8296 guards with ManagedIdentities == nil early return, so self-managed clusters (which don't use managed identities) are not covered.

How it works:

  1. Machine-level signal: When CAPZ cannot delete an Azure VM (expired credentials, deleted resource group, etc.), it sets Ready=False with Reason=DeletionFailed on the AzureMachine.

  2. Orphaned machine cleanup (DeleteOrphanedMachines): For each AzureMachine that has been deleting for >10 minutes (deletionFailedThreshold) AND has the DeletionFailed condition, the MachineFinalizer is removed — unblocking cluster teardown.

  3. CLI fix (cmd/cluster/azure/destroy.go): Handles 404 on resource group during hypershift destroy cluster azure so it continues instead of failing hard when infrastructure is already gone.

Difference from #8296:

PR #8296 (merged) This PR
Scope ARO HCP only (ManagedIdentities != nil) Self-managed Azure clusters
Signal Same: machine-level DeletionFailed condition Same
Threshold Same: 10 minutes Same
Guard ManagedIdentities == nil early return No guard — applies to all Azure clusters

The DeletionFailed condition is set by CAPZ regardless of credential type, so this approach works for both managed identity and service principal clusters.

Which issue(s) this PR fixes:

Fixes OCPBUGS-86690

Special notes for your reviewer:

  • Previous approach (credential-level ValidAzureIdentityProvider condition) has been fully removed per feedback from @enxebre. All CPO health check code, condition bubbling, and credential status types are gone.
  • This now uses the exact same pattern as OCPBUGS-63720: orphan machines when managed identity is invalid on clus… #8296 (deletionFailedThreshold + hasDeletionFailedCondition) without the ManagedIdentities guard.
  • The cmd/cluster/azure/destroy.go 404 handling fix is independent and still needed.

Checklist:

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

@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 Jun 5, 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 the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 5, 2026
@openshift-ci-robot openshift-ci-robot added jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. labels Jun 5, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@vsolanki12: This pull request references Jira Issue OCPBUGS-86690, 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)

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:

When Azure resource groups are deleted before the HostedCluster (e.g. manually or by another process), cluster deletion hangs indefinitely. Two issues cause this:

  1. CAPI AzureMachine finalizers block deletion cascade: The Azure CAPI provider controller cannot remove finalizers because the backing Azure resources no longer exist. This causes the CAPI Cluster object (and thus the HostedCluster) to wait forever for machine cleanup.

  2. CLI destroy command hard-fails on missing resource group: The hypershift destroy cluster azure command validates the resource group exists before proceeding. When the RG is already deleted, this pre-validation returns a hard error, blocking the entire destroy flow.

Fix

  • Operator: Implement the OrphanDeleter interface for Azure. During HC deletion, detect AzureMachines stuck in deletion for longer than 5 minutes and remove only the Azure-specific finalizer (azuremachine.infrastructure.cluster.x-k8s.io) using controllerutil.RemoveFinalizer, preserving any finalizers from other controllers.

  • CLI: Change the resource group pre-validation to detect 404 responses, log a warning, and continue with deletion instead of returning a hard error. Non-404 errors (e.g. auth failures) still fail.

Which issue(s) this PR fixes:

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

Special notes for your reviewer:

  • The Azure implementation follows the same pattern as the existing AWS DeleteOrphanedMachines (aws.go:380-402), but uses a time-based heuristic (5 min stuck in deletion) instead of credential status checking, since Azure doesn't expose an equivalent credential validity signal.
  • Unlike the AWS implementation which clears all finalizers (Finalizers = []string{}), this implementation only removes the Azure-specific finalizer using controllerutil.RemoveFinalizer to avoid interfering with other controllers. This improvement was identified during CodeRabbit review.
  • Tested on a live Azure HCP cluster (4.22.0) with simulated stuck AzureMachines. Operator logs confirm both machines had their Azure finalizers removed and the deletion cascade proceeded without hanging.

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 commented Jun 5, 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 PR adds Azure identity-provider validation to the control-plane healthcheck path, passes Azure credentials into that check, and propagates the resulting condition to HostedCluster status. It also adds Azure orphaned-machine cleanup logic in the hostedcluster platform code, a new HostedCluster condition type, and a destroy-path change that continues when an explicitly provided Azure resource group returns 404.

Sequence Diagram(s)

sequenceDiagram
  participant Main as control-plane-operator/main.go
  participant HCU as HealthCheckUpdater
  participant HC as azureHealthCheckIdentityProvider
  participant AzureUtil as azureutil.GetResourceGroupInfo
  participant HCP as HostedControlPlane
  Main->>Main: resolveAzureHealthCheckCredentials
  Main->>HCU: set AzureCreds
  HCU->>HC: run Azure identity check
  HC->>HCP: read platform and status
  HC->>AzureUtil: validate Azure resource group info
  AzureUtil-->>HC: success or Azure error
  HC-->>HCU: update ValidAzureIdentityProvider
Loading

Suggested reviewers

  • muraee
  • jparrill
  • sjenning
🚥 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 clearly matches the main fix: Azure cluster deletion no longer hangs when the resource group was deleted already.
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 No dynamic Ginkgo titles were added; the new tests use static t.Run names and no generated values, timestamps, or identifiers.
Test Structure And Quality ✅ Passed The new tests are plain table-driven unit tests, not Ginkgo specs; they use no cluster resources, no waits, and include clear failure messages where needed.
Topology-Aware Scheduling Compatibility ✅ Passed The PR only adds Azure health checks, condition bubbling, destroy 404 handling, and orphaned-machine cleanup; no affinity, selectors, replicas, or topology assumptions were introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PASS: The PR adds only Go unit tests (Test*), and searches found no Ginkgo It/Describe/Context/When or IPv4/external-network markers in the touched files.
No-Weak-Crypto ✅ Passed Touched hunks only add Azure identity checks and condition bubbling; searches found no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB or custom crypto in the PR changes.
Container-Privileges ✅ Passed Changed files are Go-only and none add privileged/hostNetwork/hostPID/hostIPC/allowPrivilegeEscalation/SYS_ADMIN settings; hits found were pre-existing unrelated manifests.
No-Sensitive-Data-In-Logs ✅ Passed No secrets, tokens, PII, or hostnames are logged; only operational identifiers like RG/machine names appear.
✨ 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 added area/cli Indicates the PR includes changes for CLI area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/azure PR/issue for Azure (AzurePlatform) platform and removed do-not-merge/needs-area labels Jun 5, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@vsolanki12: This pull request references Jira Issue OCPBUGS-86690, 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:

When Azure resource groups are deleted before the HostedCluster (e.g. manually or by another process), cluster deletion hangs indefinitely. Two issues cause this:

  1. CAPI AzureMachine finalizers block deletion cascade: The Azure CAPI provider controller cannot remove finalizers because the backing Azure resources no longer exist. This causes the CAPI Cluster object (and thus the HostedCluster) to wait forever for machine cleanup.

  2. CLI destroy command hard-fails on missing resource group: The hypershift destroy cluster azure command validates the resource group exists before proceeding. When the RG is already deleted, this pre-validation returns a hard error, blocking the entire destroy flow.

Fix

  • Operator: Implement the OrphanDeleter interface for Azure. During HC deletion, detect AzureMachines stuck in deletion for longer than 5 minutes and remove only the Azure-specific finalizer (azuremachine.infrastructure.cluster.x-k8s.io) using controllerutil.RemoveFinalizer, preserving any finalizers from other controllers.

  • CLI: Change the resource group pre-validation to detect 404 responses, log a warning, and continue with deletion instead of returning a hard error. Non-404 errors (e.g. auth failures) still fail.

Which issue(s) this PR fixes:

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

Special notes for your reviewer:

  • The Azure implementation follows the same pattern as the existing AWS DeleteOrphanedMachines (aws.go:380-402), but uses a time-based heuristic (5 min stuck in deletion) instead of credential status checking, since Azure doesn't expose an equivalent credential validity signal.
  • Unlike the AWS implementation which clears all finalizers (Finalizers = []string{}), this implementation only removes the Azure-specific finalizer using controllerutil.RemoveFinalizer to avoid interfering with other controllers. This improvement was identified during CodeRabbit review.
  • Tested on a live Azure HCP cluster (4.22.0) with simulated stuck AzureMachines. Operator logs confirm both machines had their Azure finalizers removed and the deletion cascade proceeded without hanging.

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

  • New Features

  • Added automatic cleanup of orphaned Azure machines stuck in deleting state for extended periods.

  • Bug Fixes

  • Improved Azure cluster destruction to gracefully handle missing resource groups.

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.

@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.70%. Comparing base (06b281a) to head (c021040).
⚠️ Report is 68 commits behind head on main.

Files with missing lines Patch % Lines
cmd/cluster/azure/destroy.go 84.61% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8682      +/-   ##
==========================================
+ Coverage   46.62%   46.70%   +0.07%     
==========================================
  Files         784      784              
  Lines       98849    98897      +48     
==========================================
+ Hits        46092    46192     +100     
+ Misses      49628    49574      -54     
- Partials     3129     3131       +2     
Files with missing lines Coverage Δ
...ers/hostedcluster/internal/platform/azure/azure.go 51.31% <100.00%> (-0.47%) ⬇️
...ollers/hostedcluster/internal/platform/platform.go 0.00% <ø> (ø)
cmd/cluster/azure/destroy.go 24.44% <84.61%> (+6.70%) ⬆️

... and 3 files with indirect coverage changes

Flag Coverage Δ
cmd-support 40.43% <84.61%> (+0.21%) ⬆️
cpo-hostedcontrolplane 48.95% <ø> (ø)
cpo-other 47.60% <ø> (ø)
hypershift-operator 57.15% <100.00%> (+0.04%) ⬆️
other 34.70% <ø> (ø)

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.

@vsolanki12
vsolanki12 force-pushed the OCPBUGS-86690-azure-orphaned-machine-deletion branch from 79425af to ba7737f Compare June 5, 2026 11:47
@vsolanki12

Copy link
Copy Markdown
Contributor Author

I have tried to reproduce and tested the fix in my test cluster as below:

Environment: Azure HCP cluster vsolanki-86690, CP version 4.22.0

Custom hypershift-operator image deployed:

    $ oc get pods -n hypershift -l name=operator \
        -o jsonpath='{range .items[*]}{.metadata.name}: {.spec.containers[0].image}{"\n"}{end}'
    operator-5c78c99-b5m4v: quay.io/vsolanki/hypershift-operator:fix-86690
    operator-5c78c99-srdl6: quay.io/vsolanki/hypershift-operator:fix-86690

Test scenario: Created two AzureMachines with azuremachine.infrastructure.cluster.x-k8s.io finalizer in the control plane namespace, then deleted them. Without a working Azure CAPI provider controller, they remain stuck in deletion indefinitely simulating the exact bug scenario where resource groups are already deleted.

Stuck AzureMachines (before HC deletion):

    $ oc get azuremachines -n clusters-vsolanki-86690 \
        -o jsonpath='{range .items[*]}{.metadata.name}: deletionTimestamp={.metadata.deletionTimestamp}, finalizers={.metadata.finalizers}{"\n"}{end}'
    test-azure-machine-1: deletionTimestamp=2026-06-05T10:39:26Z, finalizers=["azuremachine.infrastructure.cluster.x-k8s.io"]
    test-azure-machine-2: deletionTimestamp=2026-06-05T10:39:26Z, finalizers=["azuremachine.infrastructure.cluster.x-k8s.io"]

After fix triggered HC deletion, AzureMachines cleaned up:

    $ oc delete hc vsolanki-86690 -n clusters
    hostedcluster.hypershift.openshift.io "vsolanki-86690" deleted
    
    $ oc get azuremachines -n clusters-vsolanki-86690
    No resources found in clusters-vsolanki-86690 namespace.

Operator logs confirm finalizer removal:

    {"level":"info","ts":"2026-06-05T11:16:38Z",
     "msg":"Removed finalizers from orphaned AzureMachine stuck in deletion",
     "controller":"hostedcluster",
     "machine":{"name":"test-azure-machine-1","namespace":"clusters-vsolanki-86690"},
     "deletionTimestamp":"2026-06-05T10:39:26Z"}

    {"level":"info","ts":"2026-06-05T11:16:38Z",
     "msg":"Removed finalizers from orphaned AzureMachine stuck in deletion",
     "controller":"hostedcluster",
     "machine":{"name":"test-azure-machine-2","namespace":"clusters-vsolanki-86690"},
     "deletionTimestamp":"2026-06-05T10:39:26Z"}

Both AzureMachines had their finalizers removed (stuck for 37 minutes, well past the 5-minute threshold). HC deletion cascade proceeded without hanging.

@vsolanki12
vsolanki12 marked this pull request as ready for review June 5, 2026 12:00
@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 Jun 5, 2026
@openshift-ci
openshift-ci Bot requested review from Nirshal and enxebre June 5, 2026 12:01
const orphanedMachineDeletionThreshold = 5 * time.Minute

func (Azure) DeleteOrphanedMachines(ctx context.Context, c client.Client, hc *hyperv1.HostedCluster, controlPlaneNamespace string) error {
azureMachineList := capiazure.AzureMachineList{}

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.

Seems we are missing a semantic for orphan machines, similar to what we have in aws via GetCredentialStatus. That would be the criteria to let deletion move forward

@vsolanki12 vsolanki12 Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks you for guidance @enxebre, as discussed, we need to implement AWS like conditions (ValidOIDCConfiguration and ValidAWSIdentityProvider) for Azure as well, so GetCredentialStatus can read those instead of needing a timeout.

This would touch API, CPO, and HO. Should I expand this PR or close it and open a fresh one with the broader scope or Do I need another JIRA with broader change and later touch this PR?

@vsolanki12
vsolanki12 force-pushed the OCPBUGS-86690-azure-orphaned-machine-deletion branch from ba7737f to e677429 Compare July 2, 2026 15:09
@openshift-ci openshift-ci Bot added area/api Indicates the PR includes changes for the API area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation labels Jul 2, 2026
@vsolanki12 vsolanki12 changed the title OCPBUGS-86690: Fix Azure cluster deletion hang when resource groups are already deleted OCPBUGS-86690: fix Azure cluster deletion hanging when resource groups are already deleted Jul 2, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@vsolanki12: This pull request references Jira Issue OCPBUGS-86690, 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)

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:

When an Azure self-managed HCP cluster's resource groups are deleted out-of-band (e.g. via Azure portal or expired credentials), hypershift destroy cluster azure hangs indefinitely. AzureMachines get stuck in deletion because CAPI cannot communicate with the deleted Azure infrastructure, and their finalizers are never cleared.

This PR adds a ValidAzureIdentityProvider condition that mirrors the existing AWS ValidAWSIdentityProvider pattern, providing a semantic credential validity signal instead of the previously rejected time-based heuristic.

How it works:

  1. CPO health check (control-plane-operator/controllers/healthcheck/azure.go): Validates Azure credentials by making a benign Resource Group GET call via azureutil.GetResourceGroupInfo(). Detects:
  • Auth failures (401/403) → condition False
  • Deleted resource groups (404) → condition False
  • Valid credentials + existing RG → condition True
  • No credentials available → condition Unknown
  1. Condition bubbling: ValidAzureIdentityProvider is set on HCP by the CPO health check, then bubbled to HC status by the HO (same pattern as AWS).

  2. Orphaned machine cleanup (DeleteOrphanedMachines): Reads GetCredentialStatus(hc) from the HC condition. When credentials are Invalid or Unknown, clears finalizers on AzureMachines stuck in deletion — unblocking cluster teardown immediately.

  3. CLI fix (cmd/cluster/azure/destroy.go): Handles 404 on resource group during hypershift destroy cluster azure so it continues instead of failing hard when infrastructure is already gone.

Credential resolution:

  • Self-managed: azidentity.NewDefaultAzureCredential() (picks up workload identity env vars)
  • ARO HCP: dataplane.NewUserAssignedIdentityCredential() (managed identity via CSI mount)

Which issue(s) this PR fixes:

Fixes OCPBUGS-86690

Special notes for your reviewer:

  • This replaces the V1 time-based heuristic that was rejected in review. The approach now mirrors ValidAWSIdentityProvider end-to-end as requested by @enxebre and @alberto.
  • Tested end-to-end on a live Azure self-managed cluster (uaenorth). After deleting the resource group out-of-band and triggering HC deletion, the CPO detected AuthorizationFailed, set ValidAzureIdentityProvider: False, and DeleteOrphanedMachines cleared finalizers on AzureMachines immediately.
  • HO logs confirmed: "skipping deletion of AzureMachine because of invalid Azure identity provider".

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

  • New Features

  • Added an Azure identity provider validation health check for Hosted Control Planes, reporting a new ValidAzureIdentityProvider condition.

  • Health checks now resolve Azure credentials when running on Azure platforms to enable identity validation.

  • Bug Fixes

  • Azure cluster destruction now gracefully continues when the specified resource group is missing.

  • Automatically removes Azure finalizers from orphaned machines that have been deleting longer than the configured short threshold.

  • Tests

  • Added unit test coverage for orphaned-machine finalizer cleanup and Azure identity provider condition logic.

  • Refactor

  • Added a compile-time check that the Azure provider supports orphan deletion.

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

@vsolanki12
vsolanki12 force-pushed the OCPBUGS-86690-azure-orphaned-machine-deletion branch from e677429 to 5c7524b Compare July 2, 2026 15:14

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

🧹 Nitpick comments (2)
api/hypershift/v1beta1/hostedcluster_conditions.go (2)

167-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix garbled doc comment for ValidAzureIdentityProvider.

The sentence "ValidAzureIdentityProvider indicates if the Identity Provider is used by the Azure Platform credentials is" is grammatically broken ("is used by ... is healthy"). Since this is an exported API type's godoc comment, it's worth tightening for clarity.

✏️ Proposed wording fix
-	// ValidAzureIdentityProvider indicates if the Identity Provider is used by the Azure Platform credentials is
-	// healthy. The check makes a harmless Resource Group GET call to verify credentials work and infrastructure is available.
+	// ValidAzureIdentityProvider indicates whether the Identity Provider used by the Azure Platform credentials
+	// is healthy. The check makes a harmless Resource Group GET call to verify credentials work and infrastructure is available.
 	// A failure here may require external user intervention to resolve.
 	ValidAzureIdentityProvider ConditionType = "ValidAzureIdentityProvider"
🤖 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 `@api/hypershift/v1beta1/hostedcluster_conditions.go` around lines 167 - 171,
The godoc for ValidAzureIdentityProvider is grammatically broken and should be
rewritten for clarity on the exported ConditionType in
hostedcluster_conditions.go. Update the comment so it clearly states that the
condition indicates whether the Azure platform identity provider credentials are
healthy, and keep the existing explanation about the harmless Resource Group GET
check and possible user intervention.

268-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

EtcdDataEncryptionUpToDate misplaced under "PublicEndpointExposed" reasons block.

Line 268's comment scopes this const block to reasons for PublicEndpointExposed, but EtcdDataEncryptionUpToDate (a ConditionType, not a reason string for that condition) is declared inside it at Line 278. This groups an unrelated condition type under a misleading heading.

♻️ Suggested regrouping
 // Reasons for PublicEndpointExposed condition.
 const (
 	PublicEndpointSharedIngressConfiguredReason = "SharedIngressConfigured"
 	PublicEndpointTopologyPrivateReason         = "TopologyPrivate"
 	PublicEndpointConvergenceInProgressReason   = "ConvergenceInProgress"
-	// EtcdDataEncryptionUpToDate indicates whether all etcd data is encrypted with the
-	// currently active encryption key.
-	// True: all data confirmed encrypted with the active key.
-	// False: re-encryption is in progress or has failed.
-	// Absent: encryption is not configured.
-	EtcdDataEncryptionUpToDate ConditionType = "EtcdDataEncryptionUpToDate"
+)
+
+// EtcdDataEncryptionUpToDate indicates whether all etcd data is encrypted with the
+// currently active encryption key.
+// True: all data confirmed encrypted with the active key.
+// False: re-encryption is in progress or has failed.
+// Absent: encryption is not configured.
+const EtcdDataEncryptionUpToDate ConditionType = "EtcdDataEncryptionUpToDate"
🤖 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 `@api/hypershift/v1beta1/hostedcluster_conditions.go` around lines 268 - 278,
The EtcdDataEncryptionUpToDate constant is grouped under the
PublicEndpointExposed reason constants, but it represents a separate
ConditionType and should not live in that block. Move EtcdDataEncryptionUpToDate
out of the “Reasons for PublicEndpointExposed condition” const group in
hostedcluster_conditions.go and place it with the other Etcd-related condition
type declarations, keeping the
PublicEndpointSharedIngressConfiguredReason/PublicEndpointTopologyPrivateReason/PublicEndpointConvergenceInProgressReason
block focused only on PublicEndpointExposed reasons.
🤖 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.

Nitpick comments:
In `@api/hypershift/v1beta1/hostedcluster_conditions.go`:
- Around line 167-171: The godoc for ValidAzureIdentityProvider is grammatically
broken and should be rewritten for clarity on the exported ConditionType in
hostedcluster_conditions.go. Update the comment so it clearly states that the
condition indicates whether the Azure platform identity provider credentials are
healthy, and keep the existing explanation about the harmless Resource Group GET
check and possible user intervention.
- Around line 268-278: The EtcdDataEncryptionUpToDate constant is grouped under
the PublicEndpointExposed reason constants, but it represents a separate
ConditionType and should not live in that block. Move EtcdDataEncryptionUpToDate
out of the “Reasons for PublicEndpointExposed condition” const group in
hostedcluster_conditions.go and place it with the other Etcd-related condition
type declarations, keeping the
PublicEndpointSharedIngressConfiguredReason/PublicEndpointTopologyPrivateReason/PublicEndpointConvergenceInProgressReason
block focused only on PublicEndpointExposed reasons.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: d4cd767e-3318-46bf-9eea-aeb9138c4080

📥 Commits

Reviewing files that changed from the base of the PR and between ba7737f and e677429.

⛔ Files ignored due to path filters (2)
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
📒 Files selected for processing (6)
  • api/hypershift/v1beta1/hostedcluster_conditions.go
  • cmd/cluster/azure/destroy.go
  • control-plane-operator/controllers/healthcheck/azure.go
  • control-plane-operator/controllers/healthcheck/azure_test.go
  • control-plane-operator/controllers/healthcheck/healthcheck_controller.go
  • control-plane-operator/main.go
💤 Files with no reviewable changes (5)
  • control-plane-operator/controllers/healthcheck/azure_test.go
  • cmd/cluster/azure/destroy.go
  • control-plane-operator/controllers/healthcheck/azure.go
  • control-plane-operator/main.go
  • control-plane-operator/controllers/healthcheck/healthcheck_controller.go

@vsolanki12

Copy link
Copy Markdown
Contributor Author

I have tested this in test cluster.

Before fix:

  1. Created two AzureMachines with CAPI finalizers to simulate orphaned machines
$ oc apply -f - <<EOF
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureMachine
metadata:
  name: test-orphan-machine-1
  namespace: clusters-vsolanki-azure
  finalizers: ["azuremachine.infrastructure.cluster.x-k8s.io"]
  labels: {cluster.x-k8s.io/cluster-name: vsolanki-azure}
spec: {identity: None, vmSize: Standard_D4s_v3, ...}
EOF
azuremachine.infrastructure.cluster.x-k8s.io/test-orphan-machine-1 created
azuremachine.infrastructure.cluster.x-k8s.io/test-orphan-machine-2 created
  1. Deleted them & these stuck in Terminating because finalizers cannot be removed
$ oc delete azuremachine test-orphan-machine-1 test-orphan-machine-2 \
    -n clusters-vsolanki-azure
azuremachine.infrastructure.cluster.x-k8s.io "test-orphan-machine-1" deleted
azuremachine.infrastructure.cluster.x-k8s.io "test-orphan-machine-2" deleted
  1. Confirmed stuck in Terminating with finalizers
$ oc get azuremachine test-orphan-machine-1 -n clusters-vsolanki-azure \
    -o jsonpath='deletionTimestamp={.metadata.deletionTimestamp}, finalizers={.metadata.finalizers}'

deletionTimestamp=2026-07-02T14:10:23Z, finalizers=["azuremachine.infrastructure.cluster.x-k8s.io"]
Artifact: Both machines stuck
$ oc get azuremachines -n clusters-vsolanki-azure
NAME                    AGE
test-orphan-machine-1   100s   ← stuck, finalizer blocking deletion
test-orphan-machine-2    91s   ← stuck, finalizer blocking deletion

After Fix:

  1. CPO health check detects invalid Azure identity. The CPO HealthCheckUpdater calls azureutil.GetResourceGroupInfo() to validate Azure credentials. In this cluster the CPO identity lacked Reader permissions on the resource group, so the check returned AuthorizationFailed.
$ oc logs deployment/control-plane-operator -n clusters-vsolanki-azure \
    -c control-plane-operator | grep health-check

{"level":"error","ts":"2026-07-02T14:41:14Z",
 "logger":"health-check-updater",
 "msg":"Failure occurred during health checks",
 "error":"some health checks failed: azure identity provider auth failure: AuthorizationFailed"}
  1. Condition set on HCP
$ oc get hostedcontrolplane vsolanki-azure -n clusters-vsolanki-azure \
    -o jsonpath='{range .status.conditions[?(@.type=="ValidAzureIdentityProvider")]}{.type}: {.status} / {.reason} / {.message}{end}'

ValidAzureIdentityProvider: False / InvalidIdentityProvider / AuthorizationFailed
  1. Condition bubbled from HCP to HC
$ oc get hostedcluster vsolanki-azure -n clusters \
    -o jsonpath='{range .status.conditions[?(@.type=="ValidAzureIdentityProvider")]}{.type}: {.status} / {.reason} / {.message}{end}'

ValidAzureIdentityProvider: False / InvalidIdentityProvider / AuthorizationFailed
  1. Triggered HostedCluster deletion
$ oc delete hostedcluster vsolanki-azure -n clusters
hostedcluster.hypershift.openshift.io "vsolanki-azure" deleted
  1. HO logs confirm DeleteOrphanedMachines cleared finalizers instantly
$ oc logs deployment/operator -n hypershift | grep "skipping deletion"

{"level":"info","ts":"2026-07-02T14:10:47Z",
 "msg":"skipping deletion of AzureMachine because of invalid Azure identity provider",
 "controller":"hostedcluster",
 "controllerGroup":"hypershift.openshift.io",
 "controllerKind":"HostedCluster",
 "HostedCluster":{"name":"vsolanki-azure","namespace":"clusters"},
 "reconcileID":"5dfc8433-b799-489a-bf73-36d6b894cbb6",
 "machine":{"name":"test-orphan-machine-1","namespace":"clusters-vsolanki-azure"}}

{"level":"info","ts":"2026-07-02T14:10:47Z",
 "msg":"skipping deletion of AzureMachine because of invalid Azure identity provider",
 "controller":"hostedcluster",
 "controllerGroup":"hypershift.openshift.io",
 "controllerKind":"HostedCluster",
 "HostedCluster":{"name":"vsolanki-azure","namespace":"clusters"},
 "reconcileID":"5dfc8433-b799-489a-bf73-36d6b894cbb6",
 "machine":{"name":"test-orphan-machine-2","namespace":"clusters-vsolanki-azure"}}
  1. AzureMachines fully cleaned up — no hang
$ oc get azuremachines -n clusters-vsolanki-azure
No resources found in clusters-vsolanki-azure namespace.

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/rebase

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Rebasing PR onto main: workflow run

…s during cluster deletion

Replace the credential-level ValidAzureIdentityProvider signal with the
machine-level DeletionFailed condition approach (aligned with PR openshift#8296).

When an AzureMachine has been deleting for >10 minutes and CAPZ sets
Ready=False with Reason=DeletionFailed, the finalizer is removed to
unblock cluster teardown. Unlike openshift#8296 which only covers ARO HCP
(ManagedIdentities != nil), this covers self-managed Azure clusters
where credentials may expire or resource groups may be deleted
out-of-band.

Removes: ValidAzureIdentityProvider condition, CPO health check for
Azure credentials, credential status types, and condition bubbling
from HCP to HC.

Keeps: cmd/cluster/azure/destroy.go 404 handling fix.

Signed-off-by: Vimal Solanki <vsolanki@redhat.com>
@github-actions
github-actions Bot force-pushed the OCPBUGS-86690-azure-orphaned-machine-deletion branch from c021040 to 5e7b1fb Compare September 2, 2026 02:41
@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Sep 2, 2026
@openshift-ci openshift-ci Bot removed lgtm Indicates that a PR is ready to be merged. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. labels Sep 2, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD de11242 and 2 for PR HEAD c021040 in total

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/verified by @georgelipceanu

Reference here: #8682 (comment)

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

Copy link
Copy Markdown

@vsolanki12: This PR has been marked as verified by @georgelipceanu.

Details

In response to this:

/verified by @georgelipceanu

Reference here: #8682 (comment)

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.

@vismishr

vismishr commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

/lgtm

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

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD 3e504aa and 2 for PR HEAD 5e7b1fb in total

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD 166c709 and 1 for PR HEAD 5e7b1fb in total

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD c24c1ce and 0 for PR HEAD 5e7b1fb in total

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/hold

Revision 5e7b1fb was retested 3 times: holding

@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 2, 2026
@vsolanki12

Copy link
Copy Markdown
Contributor Author

/retest

@vsolanki12

Copy link
Copy Markdown
Contributor Author

/unhold

@openshift-ci openshift-ci Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Sep 3, 2026
@vsolanki12

Copy link
Copy Markdown
Contributor Author

/retest

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

/retest-required

Remaining retests: 0 against base HEAD 99ac230 and 2 for PR HEAD 5e7b1fb in total

@csrwng

csrwng commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

/override ci/prow/okd-scos-images

@openshift-ci

openshift-ci Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@csrwng: Overrode contexts on behalf of csrwng: ci/prow/okd-scos-images

Details

In response to this:

/override ci/prow/okd-scos-images

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 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@vsolanki12: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
openshift-merge-bot Bot merged commit 3d82e40 into openshift:main Sep 3, 2026
24 checks passed
@openshift-ci-robot

Copy link
Copy Markdown

@vsolanki12: Jira Issue Verification Checks: Jira Issue OCPBUGS-86690
✔️ 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-86690 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:

When an Azure self-managed HCP cluster's resource groups are deleted out-of-band (e.g. via Azure portal or expired credentials), hypershift destroy cluster azure hangs indefinitely. AzureMachines get stuck in deletion because CAPZ cannot communicate with the deleted Azure infrastructure, and their finalizers are never cleared.

This PR uses the same DeletionFailed condition approach as #8296 (ARO HCP) but extends coverage to self-managed Azure clusters. PR #8296 guards with ManagedIdentities == nil early return, so self-managed clusters (which don't use managed identities) are not covered.

How it works:

  1. Machine-level signal: When CAPZ cannot delete an Azure VM (expired credentials, deleted resource group, etc.), it sets Ready=False with Reason=DeletionFailed on the AzureMachine.

  2. Orphaned machine cleanup (DeleteOrphanedMachines): For each AzureMachine that has been deleting for >10 minutes (deletionFailedThreshold) AND has the DeletionFailed condition, the MachineFinalizer is removed — unblocking cluster teardown.

  3. CLI fix (cmd/cluster/azure/destroy.go): Handles 404 on resource group during hypershift destroy cluster azure so it continues instead of failing hard when infrastructure is already gone.

Difference from #8296:

PR #8296 (merged) This PR
Scope ARO HCP only (ManagedIdentities != nil) Self-managed Azure clusters
Signal Same: machine-level DeletionFailed condition Same
Threshold Same: 10 minutes Same
Guard ManagedIdentities == nil early return No guard — applies to all Azure clusters

The DeletionFailed condition is set by CAPZ regardless of credential type, so this approach works for both managed identity and service principal clusters.

Which issue(s) this PR fixes:

Fixes OCPBUGS-86690

Special notes for your reviewer:

  • Previous approach (credential-level ValidAzureIdentityProvider condition) has been fully removed per feedback from @enxebre. All CPO health check code, condition bubbling, and credential status types are gone.
  • This now uses the exact same pattern as OCPBUGS-63720: orphan machines when managed identity is invalid on clus… #8296 (deletionFailedThreshold + hasDeletionFailedCondition) without the ManagedIdentities guard.
  • The cmd/cluster/azure/destroy.go 404 handling fix is independent and still needed.

Checklist:

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

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

@openshift-merge-robot

Copy link
Copy Markdown
Contributor

Fix included in release 5.1.0-0.nightly-2026-09-04-051944

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/api Indicates the PR includes changes for the API area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/azure PR/issue for Azure (AzurePlatform) platform 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.

9 participants