Skip to content

CNTRLPLANE-3857: Replace per-controller HCP finalizers with status conditions for deletion cleanup - #9137

Draft
PoornimaSingour wants to merge 1 commit into
openshift:mainfrom
PoornimaSingour:CNTRLPLANE-3857
Draft

CNTRLPLANE-3857: Replace per-controller HCP finalizers with status conditions for deletion cleanup#9137
PoornimaSingour wants to merge 1 commit into
openshift:mainfrom
PoornimaSingour:CNTRLPLANE-3857

Conversation

@PoornimaSingour

@PoornimaSingour PoornimaSingour commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace per-controller HCP finalizers with a shared PrivateConnectivityCleanedUp status condition to gate HCP deletion on platform-specific cleanup completion
  • CPO deletion path now waits for the condition (with 10-minute timeout fallback) before removing the HCP finalizer, preventing credential loss on CPO restart mid-deletion
  • Azure PLS controller migrated from per-controller finalizer to the same condition-based pattern for consistency

Why

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:

  1. AWS PrivateLink: No HCP finalizer existed — 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 (CNTRLPLANE-507)
  2. Azure PLS: Used a per-controller finalizer (azure-pls-endpoint-cleanup) on the HCP, creating sprawling coupling and ordering issues

What Changed

Component Change
API Added PrivateConnectivityCleanedUp condition type
CPO HCP reconciler Gate finalizer removal on condition for private HCPs, 10-min timeout fallback
AWS PrivateLink controller Switch HCP watch to EnqueueRequestsFromMapFunc, add reconcileHCPDeletion that cleansup each CR and sets condition when all done
Azure PLS controller Replace per-controller finalizer with condition-based pattern, add legacy finalizer migration
Tests Added unit tests for all three controllers (12 new test cases)

Test Plan

  • make test — all unit tests pass
  • make verify — lint clean, generated docs up to date
  • Manual test: create private AWS cluster with fix → delete → verify PrivateConnectivityCleanedUp=True set → HCP finalizer removed → clean deletion
  • Manual test: create private AWS cluster without fix → delete → HCP deleted immediately without waiting for PrivateLink cleanup (confirms the bug)
  • e2e-aws — CI
  • e2e-azure — CI

Which issue(s) this PR fixes:

Fixes
Ref: CNTRLPLANE-3857
Supersedes: #8499

Summary by CodeRabbit

  • New Features

    • Improved cleanup of AWS and Azure private connectivity resources during HostedControlPlane deletion.
    • HostedControlPlane deletion now waits for private connectivity cleanup to complete, with a timeout safeguard.
    • Added status reporting to indicate whether private connectivity cleanup succeeded or timed out.
    • Improved migration away from legacy cleanup finalizers.
  • Bug Fixes

    • Prevented HostedControlPlane deletion from completing before associated private connectivity resources are cleaned up.
  • Tests

    • Added coverage for cleanup coordination, timeout handling, failures, and multiple connectivity resources.

…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
@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 the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 28, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 28, 2026

Copy link
Copy Markdown

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

Details

In response to this:

Summary

  • Replace per-controller HCP finalizers with a shared PrivateConnectivityCleanedUp status condition to gate HCP deletion on platform-specific cleanup completion
  • CPO deletion path now waits for the condition (with 10-minute timeout fallback) before removing the HCP finalizer, preventing credential loss on CPO restart mid-deletion
  • Azure PLS controller migrated from per-controller finalizer to the same condition-based pattern for consistency

Why

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:

  1. AWS PrivateLink: No HCP finalizer existed — 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 (CNTRLPLANE-507)
  2. Azure PLS: Used a per-controller finalizer (azure-pls-endpoint-cleanup) on the HCP, creating sprawling coupling and ordering issues

What Changed

Component Change
API Added PrivateConnectivityCleanedUp condition type
CPO HCP reconciler Gate finalizer removal on condition for private HCPs, 10-min timeout fallback
AWS PrivateLink controller Switch HCP watch to EnqueueRequestsFromMapFunc, add reconcileHCPDeletion that cleansup each CR and sets condition when all done
Azure PLS controller Replace per-controller finalizer with condition-based pattern, add legacy finalizer migration
Tests Added unit tests for all three controllers (12 new test cases)

Test Plan

  • make test — all unit tests pass
  • make verify — lint clean, generated docs up to date
  • Manual test: create private AWS cluster with fix → delete → verify PrivateConnectivityCleanedUp=True set → HCP finalizer removed → clean deletion
  • Manual test: create private AWS cluster without fix → delete → HCP deleted immediately without waiting for PrivateLink cleanup (confirms the bug)
  • e2e-aws — CI
  • e2e-azure — CI

Which issue(s) this PR fixes:

Fixes
Ref: CNTRLPLANE-3857
Supersedes: #8499

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 added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/needs-area labels Jul 28, 2026
@openshift-ci

openshift-ci Bot commented Jul 28, 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 area/api Indicates the PR includes changes for the API label Jul 28, 2026
@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: PoornimaSingour
Once this PR has been reviewed and has the lgtm label, please assign csrwng 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 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/platform/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform and removed do-not-merge/needs-area labels Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

A new PrivateConnectivityCleanedUp condition coordinates private connectivity deletion. AWS EndpointService and Azure Private Link Service controllers now reconcile resources during HostedControlPlane deletion, remove resource finalizers, and set the condition after all related resources are cleaned up. The HostedControlPlane controller waits for this condition, periodically requeues deletion, or records a timeout after ten minutes. Tests cover mapping, cleanup, finalizer migration, status updates, conflicts, and timeout behavior.

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
Loading

Suggested reviewers: bryan-cox, csrwng

🚥 Pre-merge checks | ✅ 4 | ❌ 7

❌ Failed checks (7 inconclusive)

Check name Status Explanation Resolution
Stable And Deterministic Test Names ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Test Structure And Quality ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Topology-Aware Scheduling Compatibility ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Ipv6 And Disconnected Network Test Compatibility ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
No-Weak-Crypto ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Container-Privileges ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
No-Sensitive-Data-In-Logs ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: moving HCP deletion cleanup from per-controller finalizers to a shared status condition.
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.
✨ 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.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 52.00000% with 96 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.58%. Comparing base (fe62283) to head (a4a6efa).

Files with missing lines Patch % Lines
...ollers/awsprivatelink/awsprivatelink_controller.go 37.36% 54 Missing and 3 partials ⚠️
.../controllers/azureprivatelinkservice/controller.go 63.07% 19 Missing and 5 partials ⚠️
...ostedcontrolplane/hostedcontrolplane_controller.go 65.90% 12 Missing and 3 partials ⚠️
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     
Files with missing lines Coverage Δ
...ostedcontrolplane/hostedcontrolplane_controller.go 46.38% <65.90%> (+0.35%) ⬆️
.../controllers/azureprivatelinkservice/controller.go 89.98% <63.07%> (-2.40%) ⬇️
...ollers/awsprivatelink/awsprivatelink_controller.go 42.80% <37.36%> (+1.15%) ⬆️
Flag Coverage Δ
cmd-support 38.39% <ø> (ø)
cpo-hostedcontrolplane 47.27% <65.90%> (+0.05%) ⬆️
cpo-other 45.30% <48.07%> (+0.05%) ⬆️
hypershift-operator 54.46% <ø> (ø)
other 33.07% <ø> (ø)

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.

@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 (3)
control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go (1)

4461-4469: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case exercising the wantErr path.

wantErr is declared but never set true; the only error-return branch in waitForPrivateConnectivityCleanup (the Status().Patch failure during timeout) is untested. A WithInterceptorFuncs client that fails SubResourcePatch for 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 | 🔵 Trivial

Verify platform controllers can still finish cleanup after the HCP is fully deleted.

The 10-minute timeout lets reconcileDeletion drop the finalizer and allow the HCP to be garbage-collected even if PrivateConnectivityCleanedUp never became True. If the AWS EndpointService / Azure PLS reconcilers' reconcileHCPDeletion path requires a live HCP object (e.g., to Get it and set the condition, per the mapped-HCP-watch design) in order to proceed with cleaning up and removing their own CR finalizers, a NotFound HCP 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 win

Silent failure on HCP mapping list error.

If r.List fails here, the function just returns nil with 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 sibling AWSEndpointService objects, 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

📥 Commits

Reviewing files that changed from the base of the PR and between fe62283 and a4a6efa.

⛔ Files ignored due to path filters (3)
  • 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
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_conditions.go is excluded by !vendor/**, !**/vendor/**
📒 Files selected for processing (7)
  • api/hypershift/v1beta1/hostedcluster_conditions.go
  • control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller.go
  • control-plane-operator/controllers/awsprivatelink/awsprivatelink_controller_test.go
  • control-plane-operator/controllers/azureprivatelinkservice/controller.go
  • control-plane-operator/controllers/azureprivatelinkservice/controller_test.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go

Comment on lines +2091 to +2393
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{}))
}

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.

📐 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

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

Labels

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 area/platform/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants