CNTRLPLANE-3857: Replace per-controller HCP finalizers with status conditions for deletion cleanup - #9137
Conversation
…ns for deletion cleanup Replace per-controller HCP finalizers with a shared PrivateConnectivityCleanedUp status condition to gate HCP deletion on platform-specific cleanup completion. Why: During HCP deletion, platform controllers (AWS PrivateLink, Azure PLS) need to clean up cloud resources before the HCP is removed. The previous approach used per-controller finalizers on the HCP, creating sprawling coupling and ordering issues. Worse, if the CPO restarted mid-deletion, the AWSEndpointService reconciler lost access to AWS credentials (stored in the now-deleted HCP), causing PrivateLink resources to leak. How: - Add PrivateConnectivityCleanedUp condition type to the HCP API - CPO deletion path gates HCP finalizer removal on this condition for private HCPs, with a 10-minute timeout fallback to prevent stuck deletions - AWS PrivateLink controller: switch HCP watch to EnqueueRequestsFromMapFunc, move HCP deletion check before CR finalizer addition, add reconcileHCPDeletion that cleans up each AWSEndpointService and sets the condition when all are done - Azure PLS controller: replace per-controller azure-pls-endpoint-cleanup finalizer with condition-based pattern, add legacy finalizer migration cleanup - Add comprehensive unit tests for all three controllers Ref: CNTRLPLANE-3857
|
Pipeline controller notification For optional jobs, comment This repository is configured in: LGTM mode |
|
@PoornimaSingour: This pull request references CNTRLPLANE-3857 which is a valid jira issue. Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set. DetailsIn response to this:
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. |
|
Skipping CI for Draft Pull Request. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: PoornimaSingour The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
📝 WalkthroughWalkthroughA new Sequence Diagram(s)sequenceDiagram
participant HostedControlPlane
participant PrivateConnectivityController
participant CloudProvider
participant HostedControlPlaneController
HostedControlPlane->>PrivateConnectivityController: deletion event
PrivateConnectivityController->>CloudProvider: clean private connectivity resources
CloudProvider-->>PrivateConnectivityController: cleanup complete
PrivateConnectivityController->>HostedControlPlane: set PrivateConnectivityCleanedUp=True
HostedControlPlaneController->>HostedControlPlane: read condition
HostedControlPlaneController->>HostedControlPlane: continue deletion or record timeout
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 7❌ Failed checks (7 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #9137 +/- ##
==========================================
+ Coverage 44.56% 44.58% +0.01%
==========================================
Files 774 774
Lines 97003 97140 +137
==========================================
+ Hits 43228 43307 +79
- Misses 50783 50831 +48
- Partials 2992 3002 +10
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go (1)
4461-4469: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case exercising the
wantErrpath.
wantErris declared but never settrue; the only error-return branch inwaitForPrivateConnectivityCleanup(theStatus().Patchfailure during timeout) is untested. AWithInterceptorFuncsclient that failsSubResourcePatchfor the timeout case would close this gap.🧪 Example additional case
{ name: "When status patch fails during timeout handling, it should return error", hcp: &hyperv1.HostedControlPlane{ ObjectMeta: metav1.ObjectMeta{ Name: "test-hcp", Namespace: "test-ns", Finalizers: []string{finalizer}, DeletionTimestamp: ptr.To(metav1.NewTime(time.Now().Add(-15 * time.Minute))), }, }, wantDone: false, wantErr: true, },And build that case's fake client with:
fake.NewClientBuilder(). WithScheme(api.Scheme). WithObjects(tt.hcp). WithStatusSubresource(&hyperv1.HostedControlPlane{}). WithInterceptorFuncs(interceptor.Funcs{ SubResourcePatch: func(...) error { return apierrors.NewConflict(...) }, }). Build()Also applies to: 4581-4587
🤖 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_test.go` around lines 4461 - 4469, Add a table-driven test case covering the timeout path in waitForPrivateConnectivityCleanup where the status patch fails, setting wantDone to false and wantErr to true. For that case, construct the fake client with WithStatusSubresource and an interceptor.Funcs SubResourcePatch that returns an error, while preserving the existing client setup for other cases.control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go (1)
434-495: 🩺 Stability & Availability | 🔵 TrivialVerify platform controllers can still finish cleanup after the HCP is fully deleted.
The 10-minute timeout lets
reconcileDeletiondrop the finalizer and allow the HCP to be garbage-collected even ifPrivateConnectivityCleanedUpnever becameTrue. If the AWS EndpointService / Azure PLS reconcilers'reconcileHCPDeletionpath requires a live HCP object (e.g., toGetit and set the condition, per the mapped-HCP-watch design) in order to proceed with cleaning up and removing their own CR finalizers, aNotFoundHCP after this timeout could leave PrivateLink endpoints, Private DNS zones, or VNet links permanently orphaned instead of just delayed.Please confirm the platform controllers tolerate a missing/deleted HCP during their own cleanup path, and consider whether the timeout condition should also emit an event/metric for operator visibility into potentially orphaned private-connectivity resources.
🔍 Suggested verification
#!/bin/bash # Check how AWS/Azure controllers handle a missing HCP during their HCP-deletion cleanup path. rg -n -B3 -A15 'func .*reconcileHCPDeletion' control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller.go control-plane-operator/controllers/azureprivatelinkservice/controller.go # Look for IsNotFound handling around the HCP Get call in this path. rg -n -B5 -A5 'apierrors.IsNotFound' control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller.go control-plane-operator/controllers/azureprivatelinkservice/controller.go🤖 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 434 - 495, Verify the AWS and Azure private-connectivity controllers’ reconcileHCPDeletion paths tolerate a missing HCP after HostedControlPlaneReconciler removes its finalizer; handle NotFound safely if those paths currently require the object to complete cleanup. Also add the established event or metric emission when waitForPrivateConnectivityCleanup records a timeout, preserving the existing timeout condition and deletion flow.control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller.go (1)
383-402: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSilent failure on HCP mapping list error.
If
r.Listfails here, the function just returnsnilwith no log, unlike the Azure counterpart (mapHCPToAzurePLS) which logs the error. A transient list failure during HCP deletion would silently prevent reconcile requests from being generated for siblingAWSEndpointServiceobjects, with no trace for debugging a stalled deletion.♻️ Proposed fix
awsEndpointServiceList := &hyperv1.AWSEndpointServiceList{} if err := r.List(ctx, awsEndpointServiceList, client.InNamespace(hcp.Namespace)); err != nil { + logr.FromContextOrDiscard(ctx).Error(err, "failed to list AWSEndpointService resources for HCP mapping", "namespace", hcp.Namespace) return nil }🤖 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/awsprivatelink/awsprivatelink_controller.go` around lines 383 - 402, Update mapHCPToAWSEndpointServices to log the error returned by r.List before returning nil, matching the error-reporting behavior of mapHCPToAzurePLS. Preserve the existing request generation and nil return behavior.
🤖 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/awsprivatelink/awsprivatelink_controller_test.go`:
- Around line 2091-2393: Rename the new tests and table-driven case names in
TestMapHCPToAWSEndpointServices, TestGetHostedControlPlane, and
TestAllEndpointServicesCleanedUp to follow the repository’s “When ... it should
...” convention, preserving each case’s behavior. Rename
TestReconcileHCPDeletion_CRBeingDeleted to describe the deletion condition and
expected immediate return using the same format.
---
Nitpick comments:
In
`@control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller.go`:
- Around line 383-402: Update mapHCPToAWSEndpointServices to log the error
returned by r.List before returning nil, matching the error-reporting behavior
of mapHCPToAzurePLS. Preserve the existing request generation and nil return
behavior.
In
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go`:
- Around line 4461-4469: Add a table-driven test case covering the timeout path
in waitForPrivateConnectivityCleanup where the status patch fails, setting
wantDone to false and wantErr to true. For that case, construct the fake client
with WithStatusSubresource and an interceptor.Funcs SubResourcePatch that
returns an error, while preserving the existing client setup for other cases.
In
`@control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go`:
- Around line 434-495: Verify the AWS and Azure private-connectivity
controllers’ reconcileHCPDeletion paths tolerate a missing HCP after
HostedControlPlaneReconciler removes its finalizer; handle NotFound safely if
those paths currently require the object to complete cleanup. Also add the
established event or metric emission when waitForPrivateConnectivityCleanup
records a timeout, preserving the existing timeout condition and deletion flow.
🪄 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: ed54732e-c0b6-407d-9d13-f6f5f363e093
⛔ Files ignored due to path filters (3)
docs/content/reference/aggregated-docs.mdis excluded by!docs/content/reference/aggregated-docs.mddocs/content/reference/api.mdis excluded by!docs/content/reference/api.mdvendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_conditions.gois excluded by!vendor/**,!**/vendor/**
📒 Files selected for processing (7)
api/hypershift/v1beta1/hostedcluster_conditions.gocontrol-plane-operator/controllers/awsprivatelink/awsprivatelink_controller.gocontrol-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.gocontrol-plane-operator/controllers/azureprivatelinkservice/controller.gocontrol-plane-operator/controllers/azureprivatelinkservice/controller_test.gocontrol-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.gocontrol-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
| func TestMapHCPToAWSEndpointServices(t *testing.T) { | ||
| now := metav1.NewTime(time.Now()) | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| obj crclient.Object | ||
| existingObjects []crclient.Object | ||
| expectedLen int | ||
| }{ | ||
| { | ||
| name: "non-HCP object returns nil", | ||
| obj: &corev1.Service{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "some-service", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| expectedLen: 0, | ||
| }, | ||
| { | ||
| name: "HCP without deletion timestamp returns nil", | ||
| obj: &hyperv1.HostedControlPlane{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "test-hcp", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| expectedLen: 0, | ||
| }, | ||
| { | ||
| name: "HCP being deleted returns requests for all endpoint services", | ||
| obj: &hyperv1.HostedControlPlane{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "test-hcp", | ||
| Namespace: "test-ns", | ||
| DeletionTimestamp: &now, | ||
| Finalizers: []string{"some-finalizer"}, | ||
| }, | ||
| }, | ||
| existingObjects: []crclient.Object{ | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "kube-apiserver-private", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "private-router", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| }, | ||
| expectedLen: 2, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| g := NewGomegaWithT(t) | ||
|
|
||
| scheme := runtime.NewScheme() | ||
| _ = hyperv1.AddToScheme(scheme) | ||
| _ = corev1.AddToScheme(scheme) | ||
|
|
||
| fakeClient := fake.NewClientBuilder(). | ||
| WithScheme(scheme). | ||
| WithObjects(tt.existingObjects...). | ||
| Build() | ||
|
|
||
| r := &AWSEndpointServiceReconciler{ | ||
| Client: fakeClient, | ||
| } | ||
|
|
||
| mapFn := r.mapHCPToAWSEndpointServices() | ||
| ctx := ctrl.LoggerInto(t.Context(), ctrl.Log.WithName("test")) | ||
| requests := mapFn(ctx, tt.obj) | ||
|
|
||
| g.Expect(requests).To(HaveLen(tt.expectedLen)) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestGetHostedControlPlane(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| objects []crclient.Object | ||
| expectHCP bool | ||
| expectError bool | ||
| }{ | ||
| { | ||
| name: "no HCPs returns nil", | ||
| objects: nil, | ||
| expectHCP: false, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "one HCP returns it", | ||
| objects: []crclient.Object{ | ||
| &hyperv1.HostedControlPlane{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "test-hcp", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| }, | ||
| expectHCP: true, | ||
| expectError: false, | ||
| }, | ||
| { | ||
| name: "multiple HCPs returns error", | ||
| objects: []crclient.Object{ | ||
| &hyperv1.HostedControlPlane{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "hcp-1", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| &hyperv1.HostedControlPlane{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "hcp-2", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| }, | ||
| expectHCP: false, | ||
| expectError: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| g := NewGomegaWithT(t) | ||
|
|
||
| scheme := runtime.NewScheme() | ||
| _ = hyperv1.AddToScheme(scheme) | ||
|
|
||
| fakeClient := fake.NewClientBuilder(). | ||
| WithScheme(scheme). | ||
| WithObjects(tt.objects...). | ||
| Build() | ||
|
|
||
| r := &AWSEndpointServiceReconciler{ | ||
| Client: fakeClient, | ||
| } | ||
|
|
||
| ctx := ctrl.LoggerInto(t.Context(), ctrl.Log.WithName("test")) | ||
| hcp, err := r.getHostedControlPlane(ctx, "test-ns") | ||
|
|
||
| if tt.expectError { | ||
| g.Expect(err).To(HaveOccurred()) | ||
| g.Expect(err.Error()).To(ContainSubstring("unexpected number of HostedControlPlanes")) | ||
| } else { | ||
| g.Expect(err).ToNot(HaveOccurred()) | ||
| } | ||
|
|
||
| if tt.expectHCP { | ||
| g.Expect(hcp).ToNot(BeNil()) | ||
| g.Expect(hcp.Name).To(Equal("test-hcp")) | ||
| } else if !tt.expectError { | ||
| g.Expect(hcp).To(BeNil()) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestAllEndpointServicesCleanedUp(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| selfName string | ||
| objects []crclient.Object | ||
| expected bool | ||
| expectError bool | ||
| }{ | ||
| { | ||
| name: "no other CRs returns true", | ||
| selfName: "my-service", | ||
| objects: []crclient.Object{ | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-service", | ||
| Namespace: "test-ns", | ||
| Finalizers: []string{finalizer}, | ||
| }, | ||
| }, | ||
| }, | ||
| expected: true, | ||
| }, | ||
| { | ||
| name: "other CRs without finalizer returns true", | ||
| selfName: "my-service", | ||
| objects: []crclient.Object{ | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-service", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "other-service", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| }, | ||
| expected: true, | ||
| }, | ||
| { | ||
| name: "other CR with finalizer returns false", | ||
| selfName: "my-service", | ||
| objects: []crclient.Object{ | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-service", | ||
| Namespace: "test-ns", | ||
| }, | ||
| }, | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "other-service", | ||
| Namespace: "test-ns", | ||
| Finalizers: []string{finalizer}, | ||
| }, | ||
| }, | ||
| }, | ||
| expected: false, | ||
| }, | ||
| { | ||
| name: "self with finalizer is skipped", | ||
| selfName: "my-service", | ||
| objects: []crclient.Object{ | ||
| &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "my-service", | ||
| Namespace: "test-ns", | ||
| Finalizers: []string{finalizer}, | ||
| }, | ||
| }, | ||
| }, | ||
| expected: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| g := NewGomegaWithT(t) | ||
|
|
||
| scheme := runtime.NewScheme() | ||
| _ = hyperv1.AddToScheme(scheme) | ||
|
|
||
| fakeClient := fake.NewClientBuilder(). | ||
| WithScheme(scheme). | ||
| WithObjects(tt.objects...). | ||
| Build() | ||
|
|
||
| r := &AWSEndpointServiceReconciler{ | ||
| Client: fakeClient, | ||
| } | ||
|
|
||
| ctx := ctrl.LoggerInto(t.Context(), ctrl.Log.WithName("test")) | ||
| result, err := r.allEndpointServicesCleanedUp(ctx, "test-ns", tt.selfName) | ||
|
|
||
| if tt.expectError { | ||
| g.Expect(err).To(HaveOccurred()) | ||
| } else { | ||
| g.Expect(err).ToNot(HaveOccurred()) | ||
| g.Expect(result).To(Equal(tt.expected)) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestReconcileHCPDeletion_CRBeingDeleted(t *testing.T) { | ||
| g := NewGomegaWithT(t) | ||
|
|
||
| now := metav1.NewTime(time.Now()) | ||
|
|
||
| awsEndpointService := &hyperv1.AWSEndpointService{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "private-router", | ||
| Namespace: "test-ns", | ||
| DeletionTimestamp: &now, | ||
| Finalizers: []string{finalizer}, | ||
| }, | ||
| } | ||
|
|
||
| hcp := &hyperv1.HostedControlPlane{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "test-hcp", | ||
| Namespace: "test-ns", | ||
| DeletionTimestamp: &now, | ||
| Finalizers: []string{"some-finalizer"}, | ||
| }, | ||
| } | ||
|
|
||
| r := &AWSEndpointServiceReconciler{} | ||
|
|
||
| ctx := ctrl.LoggerInto(t.Context(), ctrl.Log.WithName("test")) | ||
| result, err := r.reconcileHCPDeletion(ctx, awsEndpointService, hcp, ctrl.Log.WithName("test")) | ||
|
|
||
| g.Expect(err).ToNot(HaveOccurred()) | ||
| g.Expect(result).To(Equal(ctrl.Result{})) | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
New tests don't follow the repo's "When ... it should ..." test naming convention.
Test case names (e.g. "non-HCP object returns nil", "no HCPs returns nil", "other CR with finalizer returns false") and the function TestReconcileHCPDeletion_CRBeingDeleted don't follow the "When ... it should ..." format. The sibling Azure test file in this same PR uses it consistently (e.g. TestReconcileHCPDeletion_WhenCRIsBeingDeleted_ItShouldReturnImmediately, "When HCP is being deleted and PLS CRs exist, it should return requests for all PLS CRs").
As per path instructions, "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
`@control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go`
around lines 2091 - 2393, Rename the new tests and table-driven case names in
TestMapHCPToAWSEndpointServices, TestGetHostedControlPlane, and
TestAllEndpointServicesCleanedUp to follow the repository’s “When ... it should
...” convention, preserving each case’s behavior. Rename
TestReconcileHCPDeletion_CRBeingDeleted to describe the deletion condition and
expected immediate return using the same format.
Source: Path instructions
Summary
PrivateConnectivityCleanedUpstatus condition to gate HCP deletion on platform-specific cleanup completionWhy
During HCP deletion, platform controllers (AWS PrivateLink, Azure PLS) need to clean up cloud resources before the HCP is removed. The previous approach had two problems:
azure-pls-endpoint-cleanup) on the HCP, creating sprawling coupling and ordering issuesWhat Changed
PrivateConnectivityCleanedUpcondition typeEnqueueRequestsFromMapFunc, addreconcileHCPDeletionthat cleansup each CR and sets condition when all doneTest Plan
make test— all unit tests passmake verify— lint clean, generated docs up to datePrivateConnectivityCleanedUp=Trueset → HCP finalizer removed → clean deletione2e-aws— CIe2e-azure— CIWhich issue(s) this PR fixes:
Fixes
Ref: CNTRLPLANE-3857
Supersedes: #8499
Summary by CodeRabbit
New Features
Bug Fixes
Tests