Skip to content

Add failure domain lifecycle support: backoff reset, orphan cleanup, …#348

Draft
jcpowermac wants to merge 2 commits into
openshift:mainfrom
jcpowermac:multi-vcenter-failure-domain-lifecycle
Draft

Add failure domain lifecycle support: backoff reset, orphan cleanup, …#348
jcpowermac wants to merge 2 commits into
openshift:mainfrom
jcpowermac:multi-vcenter-failure-domain-lifecycle

Conversation

@jcpowermac

@jcpowermac jcpowermac commented Jun 10, 2026

Copy link
Copy Markdown

…topology tests

Phase 1: Fix StorageClassController backoff — reset to 1-minute interval on success instead of permanently staying at the 30-minute cap after exponential backoff.

Phase 3A: Orphan tag cleanup — detect datastores tagged with the cluster tag that are no longer referenced by any failure domain, detach orphan tags with PV safety check (skip detach when bound PVs exist), add tag operation and orphan detection Prometheus metrics, integrate cleanup into createStoragePolicy() to handle zonal-to-non-zonal transitions (2+ FDs to 0 FDs). Support force-cleanup via Infrastructure annotation.

Add topology mode transition verification tests confirming the hook correctly handles FD count transitions (2->1->2->0).

Summary by CodeRabbit

  • New Features
    • Added Day-2 orphan-tag cleanup for datastore tags during storage policy operations, including PV-safety-aware detaching and force-cleanup behavior.
    • Added an operator condition to indicate when orphan cleanup is pending.
    • Added metrics for tag operations and total orphaned-tag detections.
  • Bug Fixes
    • Improved storage-policy resync scheduling with per-vCenter backoff (fixed success interval, exponential failure backoff) and stale state cleanup when vCenters disconnect.
    • Orphan detection/cleanup now runs only when the Day-2 gate is enabled.
  • Tests
    • Expanded unit and e2e coverage for per-vCenter backoff, orphan-cleanup gating, SPBM edge cases, topology feature-gate transitions, and failure-domain lifecycle.

@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 10, 2026
@openshift-ci

openshift-ci Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci

openshift-ci Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: jcpowermac
Once this PR has been reviewed and has the lgtm label, please assign dobsonj 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

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

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

The controller now tracks per-vCenter backoff and orphan state, adds day-2 orphan cleanup with PV-safety checks, threads feature-gate configuration through vSphere wiring, and expands unit and e2e coverage for topology and failure-domain lifecycle behavior.

Changes

Storage policy day-2 cleanup

Layer / File(s) Summary
Controller state and sync flow
pkg/operator/storageclasscontroller/storageclasscontroller.go
Stores per-vCenter backoff and orphan counts, prunes stale vCenter entries, and updates conditions from aggregated pending orphan totals.
Orphan cleanup and metrics
pkg/operator/storageclasscontroller/vmware.go, pkg/operator/utils/metric.go
Adds day-2 storage-policy cleanup state, orphan detection and detach logic, PV-safety checks, tag-operation metrics, and orphan-tag metrics.
Storage policy controller tests
pkg/operator/storageclasscontroller/storageclasscontroller_test.go
Adds backoff reset, per-vCenter backoff, and pending-orphan resync coverage with updated test fakes.
vSphere wiring and topology tests
pkg/operator/vspherecontroller/vspherecontroller.go, pkg/operator/vspherecontroller/driver_starter_test.go
Passes feature gates and updated constructor arguments through vSphere setup and adds topology-hook tests.
Storage policy unit coverage
pkg/operator/storageclasscontroller/vmware_test.go
Covers storage-policy preservation, vCenter map cleanup, day-2 disabled behavior, and failure-domain filtering.
Failure-domain lifecycle e2e
test/e2e/failure_domain_lifecycle_test.go
Adds day-2 gated failure-domain removal tests, orphan-condition polling, PVC helpers, and operator-health polling.

Dependency updates

Layer / File(s) Summary
Main module bumps
go.mod
Updates pinned versions for github.com/openshift/api and k8s.io/kube-openapi.
openshift-tests module bumps
openshift-tests/go.mod
Updates indirect dependency versions and removes some previously listed indirect entries.

Estimated code review effort: 4 (Complex) | ~60 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 3 warnings)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error New logs print vCenter hostnames, datacenter/datastore names, and storage policy names in test/e2e and controller code. Redact identifiers or log only counts/opaque IDs; remove host/datacenter/datastore/policy names from t.Log*, klog*, and recorder messages.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning Several e2e polling helpers swallow API errors, and the 'transition' test only reads current state instead of exercising a topology change. Return poll errors (except expected NotFound) and rewrite TestStorageClassSurvivesTopologyTransition to mutate failure domains and verify each step.
Microshift Test Compatibility ⚠️ Warning The new e2e tests use config.openshift.io APIs (FeatureGates, Infrastructure, ClusterOperators) with no MicroShift skip/tag guard. Add [apigroup:config.openshift.io] or [Skipped:MicroShift], or guard with exutil.IsMicroShiftCluster() and g.Skip(), for these e2e tests.
✅ 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 change set: failure-domain lifecycle work, backoff reset, and orphan cleanup.
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 All added test titles are static Go test names or fixed subtest strings; no dynamic values, timestamps, UUIDs, or generated names appear in titles.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The new e2e tests only mutate Infrastructure/FDs and create a single PVC; they don’t require multiple nodes, failover, or any SNO-specific exclusion.
Topology-Aware Scheduling Compatibility ✅ Passed No new node selectors, anti-affinity, topology spread, or replica heuristics were introduced; the PR only adds CSI feature-gate and storage-policy logic.
Ote Binary Stdout Contract ✅ Passed No stdout writes were added in process-level code; the only fmt.Print* hits are in regular helpers/tests, and the lone init() just registers metrics.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The new e2e tests only use cluster APIs/PVCs and contain no IPv4 literals, IPv4-only parsing, host/IP URL building, or external internet dependencies.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret comparisons appear in the touched files; exact-match scans returned no hits.
Container-Privileges ✅ Passed No changed PR file introduces privileged, host*, SYS_ADMIN, runAsUser:0, or allowPrivilegeEscalation settings; the diff is controller/test code.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
pkg/operator/storageclasscontroller/storageclasscontroller.go (1)

167-175: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Backoff reset happens after scheduling, so a successful sync can still wait at the capped delay.

At Line 167-Line 170, nextCheck is computed before the success check, and only then reset at Line 175. After repeated failures, the first success can still schedule the next run near 30 minutes, instead of restarting from ~1 minute. Move scheduling into success/failure branches and compute the success delay from defaultBackoff.

Suggested fix
-	nextRunDelay := c.backoff.Step()
-	c.lastCheck = time.Now()
-	c.nextCheck = c.lastCheck.Add(nextRunDelay)
-
-	if err != nil {
-		return "", checks.MakeGenericVCenterAPIError(err)
-	}
-	// Reset backoff on success — next sync restarts at 1-minute interval
-	c.backoff = defaultBackoff
+	c.lastCheck = time.Now()
+	if err != nil {
+		nextRunDelay := c.backoff.Step()
+		c.nextCheck = c.lastCheck.Add(nextRunDelay)
+		return "", checks.MakeGenericVCenterAPIError(err)
+	}
+	// Reset backoff on success and schedule next run from default interval.
+	c.backoff = defaultBackoff
+	c.nextCheck = c.lastCheck.Add(wait.Jitter(defaultBackoff.Duration, defaultBackoff.Jitter))
 	return policyName, checks.MakeClusterCheckResultPass()
🤖 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 `@pkg/operator/storageclasscontroller/storageclasscontroller.go` around lines
167 - 175, The scheduling (c.lastCheck/c.nextCheck) is computed before checking
err, so after failures a subsequent success still uses the large backoff; move
the time scheduling into the success/failure branches inside the function (where
err is evaluated) so that on error you call c.backoff = c.backoff.Step(), set
c.lastCheck = time.Now() and c.nextCheck = c.lastCheck.Add(c.backoff) (and
return the MakeGenericVCenterAPIError), while on success you reset c.backoff =
defaultBackoff and compute c.lastCheck = time.Now() and c.nextCheck =
c.lastCheck.Add(defaultBackoff) so the next sync restarts at the short interval;
update locations referencing c.backoff, c.lastCheck, c.nextCheck,
defaultBackoff, and checks.MakeGenericVCenterAPIError accordingly.
🧹 Nitpick comments (1)
pkg/operator/storageclasscontroller/vmware.go (1)

626-629: ⚡ Quick win

Silently ignoring errors when listing datastores may hide issues.

If DatastoreList fails for a datacenter (line 626-627), the error is silently swallowed with continue. This could mask connectivity issues or permission problems, making debugging harder if a datastore's datacenter cannot be resolved.

Consider logging at debug level before continuing:

Suggested fix
 		dsList, err := dcFinder.DatastoreList(ctx, "*")
 		if err != nil {
+			klog.V(4).Infof("Could not list datastores in datacenter %s: %v", dc.Name(), err)
 			continue
 		}
🤖 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 `@pkg/operator/storageclasscontroller/vmware.go` around lines 626 - 629, The
loop silently swallows errors from dcFinder.DatastoreList(ctx, "*"); instead,
before the continue log the failure at debug/verbose level including the
datacenter identifier and the error (e.g. reference dcFinder or the local
datacenter variable and err) so you can still continue on error but capture
context; update the block around dcFinder.DatastoreList(ctx, "*") to call the
existing logger (or klog) with a descriptive message plus err, then continue as
before.
🤖 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 `@pkg/operator/storageclasscontroller/vmware.go`:
- Around line 519-527: The orphanedDatastore.HasBoundPVs flag is never set in
findOrphanedTags, so detachOrphanTags' safety check cannot skip datastores with
bound PVs; modify findOrphanedTags to determine whether each orphaned datastore
has any bound PersistentVolumes (e.g., query the Kubernetes API for PVs that
reference the datastore identifier or use any existing helper that maps PVs to
datastores) and set orphanedDatastore.HasBoundPVs accordingly when constructing
each entry; keep the rest of the logic in detachOrphanTags unchanged so its
existing check on HasBoundPVs will correctly skip detaching datastores that have
bound PVs.
- Around line 562-567: The orphan-detection builds currentFDs from
v.infra.Spec.PlatformSpec.VSphere.FailureDomains which includes failure domains
for all vCenters; change the logic (in the method that populates currentFDs,
e.g., findOrphanedTags) to iterate over the already-filtered v.failureDomains
(set in NewStoragePolicyAPI) and insert fd.Topology.Datacenter + "/" +
fd.Topology.Datastore into currentFDs so only failure domains for this
storagePolicyAPI's vCenter (v.connection.Hostname) are considered.

In `@pkg/operator/vspherecontroller/driver_starter_test.go`:
- Around line 131-152: Tests currently duplicate logic by manually appending
topology args using utils.GetTopologyCategories(...) instead of exercising the
topologyHook, so they won't catch regressions; update the test to call
topologyHook (pass the deployment.Spec.Template.Spec.Containers or the container
slice and the clusterCSIDriver/infraObj as required by topologyHook) and then
assert on the returned/modified container args for the "csi-provisioner"
container: for the branch with >=2 topology categories assert it contains
"--feature-gates=Topology=true" and "--strict-topology", and for the 0/1 branch
assert it contains "--feature-gates=Topology=false"; remove the manual append
logic using utils.GetTopologyCategories(...) and drive both scenarios via
topologyHook to validate container selection and correct flag values.

---

Outside diff comments:
In `@pkg/operator/storageclasscontroller/storageclasscontroller.go`:
- Around line 167-175: The scheduling (c.lastCheck/c.nextCheck) is computed
before checking err, so after failures a subsequent success still uses the large
backoff; move the time scheduling into the success/failure branches inside the
function (where err is evaluated) so that on error you call c.backoff =
c.backoff.Step(), set c.lastCheck = time.Now() and c.nextCheck =
c.lastCheck.Add(c.backoff) (and return the MakeGenericVCenterAPIError), while on
success you reset c.backoff = defaultBackoff and compute c.lastCheck =
time.Now() and c.nextCheck = c.lastCheck.Add(defaultBackoff) so the next sync
restarts at the short interval; update locations referencing c.backoff,
c.lastCheck, c.nextCheck, defaultBackoff, and checks.MakeGenericVCenterAPIError
accordingly.

---

Nitpick comments:
In `@pkg/operator/storageclasscontroller/vmware.go`:
- Around line 626-629: The loop silently swallows errors from
dcFinder.DatastoreList(ctx, "*"); instead, before the continue log the failure
at debug/verbose level including the datacenter identifier and the error (e.g.
reference dcFinder or the local datacenter variable and err) so you can still
continue on error but capture context; update the block around
dcFinder.DatastoreList(ctx, "*") to call the existing logger (or klog) with a
descriptive message plus err, then continue as before.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fbc47b8f-6095-4076-9c5b-e1654313200f

📥 Commits

Reviewing files that changed from the base of the PR and between ef4e3b6 and 9ca33e6.

📒 Files selected for processing (5)
  • pkg/operator/storageclasscontroller/storageclasscontroller.go
  • pkg/operator/storageclasscontroller/storageclasscontroller_test.go
  • pkg/operator/storageclasscontroller/vmware.go
  • pkg/operator/utils/metric.go
  • pkg/operator/vspherecontroller/driver_starter_test.go

Comment thread pkg/operator/storageclasscontroller/vmware.go
Comment thread pkg/operator/storageclasscontroller/vmware.go Outdated
Comment thread pkg/operator/vspherecontroller/driver_starter_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (4)
test/e2e/failure_domain_lifecycle_test.go (4)

196-199: ⚡ Quick win

Log PVC deletion error in cleanup.

The Delete error is silently ignored. While cleanup failures shouldn't fail the test, logging them helps diagnose issues when tests leave artifacts behind.

Suggested fix
 	defer func() {
 		t.Log("Cleaning up test PVC")
-		kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName, metav1.DeleteOptions{})
+		if err := kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName, metav1.DeleteOptions{}); err != nil {
+			t.Logf("Warning: failed to delete test PVC %s: %v", pvcName, err)
+		}
 	}()
🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 196 - 199, The
deferred cleanup currently ignores errors from the PVC deletion; change the
defer func so it captures the returned error from
kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName,
metav1.DeleteOptions{}) and log it (e.g., via t.Logf or t.Errorf) with context
including ns and pvcName; keep the cleanup non-fatal (do not call t.FailNow) but
ensure any non-nil err is reported so test artifacts can be diagnosed.

Sources: Coding guidelines, Linters/SAST tools


252-270: ⚡ Quick win

Remove unused operatorClient parameter.

The operatorClient *operatorclient.Clientset parameter is declared but never used within the function body. This appears to be leftover from development.

Suggested fix
-func waitForOperatorHealthy(t *testing.T, ctx context.Context, configClient *configclient.Clientset, operatorClient *operatorclient.Clientset) {
+func waitForOperatorHealthy(t *testing.T, ctx context.Context, configClient *configclient.Clientset) {

Then update all call sites (lines 109, 114, 235, 239) to remove the ocpOperatorClient argument.

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 252 - 270, The
function signature waitForOperatorHealthy currently declares an unused parameter
operatorClient *operatorclient.Clientset; remove that parameter from the
function signature and from its parameter list, and update every call site that
passes ocpOperatorClient to waitForOperatorHealthy to stop passing that argument
so calls match the new signature (keep ctx, t, configClient arguments intact).
Ensure imports remain correct after removing the unused parameter.

229-236: ⚡ Quick win

Handle errors in deferred FD restore to match the pattern in TestFailureDomainRemovalTagCleanup.

Unlike the first test (lines 96-110) which properly checks and logs errors, this defer silently discards both the buildFDPatch error and the Patch error. This inconsistency makes debugging harder when restores fail.

Suggested fix
 	defer func() {
 		t.Log("Restoring original failure domains")
-		restoreJSON, _ := buildFDPatch(originalFDs)
-		configClient.ConfigV1().Infrastructures().Patch(
+		restoreJSON, err := buildFDPatch(originalFDs)
+		if err != nil {
+			t.Errorf("Failed to build restore patch: %v", err)
+			return
+		}
+		_, restoreErr := configClient.ConfigV1().Infrastructures().Patch(
 			ctx, "cluster", types.MergePatchType, restoreJSON, metav1.PatchOptions{},
 		)
+		if restoreErr != nil {
+			t.Errorf("Failed to restore failure domains: %v", restoreErr)
+		}
 		waitForOperatorHealthy(t, ctx, configClient, ocpOperatorClient)
 	}()
🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 229 - 236, The
deferred restore silently ignores errors; change it to check and log both
buildFDPatch and Patch errors like the other test: call
buildFDPatch(originalFDs) and if it returns an error log it via t.Logf/t.Errorf
and return from the defer, otherwise call
configClient.ConfigV1().Infrastructures().Patch(...) and capture its error,
logging that error as well; only call waitForOperatorHealthy(t, ctx,
configClient, ocpOperatorClient) when the patch succeeds. Reference:
buildFDPatch, restoreJSON, configClient.ConfigV1().Infrastructures().Patch, and
waitForOperatorHealthy.

Sources: Coding guidelines, Linters/SAST tools


283-285: 💤 Low value

Log PVC deletion error.

Consistent with the other cleanup patterns, log the error if deletion fails.

Suggested fix
 	defer func() {
-		kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName, metav1.DeleteOptions{})
+		if err := kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName, metav1.DeleteOptions{}); err != nil {
+			t.Logf("Warning: failed to delete test PVC %s: %v", pvcName, err)
+		}
 	}()
🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 283 - 285, The defer
currently drops any error when deleting the PVC; update the defer to call
kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName,
metav1.DeleteOptions{}) into an error variable and, if non-nil, log it (e.g.,
using t.Logf or the test logger) with the pvcName, ns and error details so
failures are recorded; reference the existing symbols
kubeClient.CoreV1().PersistentVolumeClaims, pvcName, ns, ctx and
metav1.DeleteOptions{} when making this change.

Source: Linters/SAST tools

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go`:
- Around line 196-199: The deferred cleanup currently ignores errors from the
PVC deletion; change the defer func so it captures the returned error from
kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName,
metav1.DeleteOptions{}) and log it (e.g., via t.Logf or t.Errorf) with context
including ns and pvcName; keep the cleanup non-fatal (do not call t.FailNow) but
ensure any non-nil err is reported so test artifacts can be diagnosed.
- Around line 252-270: The function signature waitForOperatorHealthy currently
declares an unused parameter operatorClient *operatorclient.Clientset; remove
that parameter from the function signature and from its parameter list, and
update every call site that passes ocpOperatorClient to waitForOperatorHealthy
to stop passing that argument so calls match the new signature (keep ctx, t,
configClient arguments intact). Ensure imports remain correct after removing the
unused parameter.
- Around line 229-236: The deferred restore silently ignores errors; change it
to check and log both buildFDPatch and Patch errors like the other test: call
buildFDPatch(originalFDs) and if it returns an error log it via t.Logf/t.Errorf
and return from the defer, otherwise call
configClient.ConfigV1().Infrastructures().Patch(...) and capture its error,
logging that error as well; only call waitForOperatorHealthy(t, ctx,
configClient, ocpOperatorClient) when the patch succeeds. Reference:
buildFDPatch, restoreJSON, configClient.ConfigV1().Infrastructures().Patch, and
waitForOperatorHealthy.
- Around line 283-285: The defer currently drops any error when deleting the
PVC; update the defer to call
kubeClient.CoreV1().PersistentVolumeClaims(ns).Delete(ctx, pvcName,
metav1.DeleteOptions{}) into an error variable and, if non-nil, log it (e.g.,
using t.Logf or the test logger) with the pvcName, ns and error details so
failures are recorded; reference the existing symbols
kubeClient.CoreV1().PersistentVolumeClaims, pvcName, ns, ctx and
metav1.DeleteOptions{} when making this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b24a8bbc-399c-4aa0-a280-33f9fe7235a0

📥 Commits

Reviewing files that changed from the base of the PR and between 9ca33e6 and 5d47637.

📒 Files selected for processing (5)
  • pkg/operator/storageclasscontroller/storageclasscontroller.go
  • pkg/operator/storageclasscontroller/storageclasscontroller_test.go
  • pkg/operator/storageclasscontroller/vmware.go
  • pkg/operator/storageclasscontroller/vmware_test.go
  • test/e2e/failure_domain_lifecycle_test.go

Comment thread pkg/operator/storageclasscontroller/storageclasscontroller.go
@jcpowermac

Copy link
Copy Markdown
Author

@coderabbitai review

/test unit
/test e2e-vsphere-csi-vcf9
/test e2e-vsphere-operator-test

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

@jcpowermac I'll review the changes in this PR now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@pkg/operator/vspherecontroller/driver_starter_test.go`:
- Around line 224-225: The call to testlib.AddInitialObjects in
TestTopologyHookSequence currently ignores its error return; capture the
returned error (from testlib.AddInitialObjects(append(initialObjects,
clusterCSIDriver), commonApiClient)) and handle it immediately (e.g., if err !=
nil { t.Fatalf("AddInitialObjects failed: %v", err) } or use your test helper
like require.NoError) before proceeding to testlib.WaitForSync with stopCh so
test setup failures fail fast and don't produce false positives.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 28e5ea56-212b-436a-a091-f8faf31839f8

📥 Commits

Reviewing files that changed from the base of the PR and between 5d47637 and 23d291e.

📒 Files selected for processing (4)
  • pkg/operator/storageclasscontroller/storageclasscontroller.go
  • pkg/operator/storageclasscontroller/storageclasscontroller_test.go
  • pkg/operator/storageclasscontroller/vmware.go
  • pkg/operator/vspherecontroller/driver_starter_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/operator/storageclasscontroller/storageclasscontroller.go
  • pkg/operator/storageclasscontroller/vmware.go
  • pkg/operator/storageclasscontroller/storageclasscontroller_test.go

Comment thread pkg/operator/vspherecontroller/driver_starter_test.go Outdated
@jcpowermac

Copy link
Copy Markdown
Author

/test unit

@jcpowermac

Copy link
Copy Markdown
Author

/test e2e-vsphere-operator-test
/test e2e-vsphere-csi-vcf9

@jcpowermac

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (2)
test/e2e/failure_domain_lifecycle_test.go (2)

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

Namespace literal duplicated.

"openshift-cluster-csi-drivers" is hardcoded in two places. Extracting a package-level constant would avoid future drift.

♻️ Proposed refactor
+const csiDriverNamespace = "openshift-cluster-csi-drivers"
+
 ...
-	ns := "openshift-cluster-csi-drivers"
+	ns := csiDriverNamespace

Also applies to: 312-312

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` at line 214, The namespace value
is duplicated as a string literal in the failure domain lifecycle tests; extract
it into a package-level constant and reuse it everywhere. Update the references
in the test helpers/flows around the ns assignment so both occurrences use the
same constant, preventing the duplicated literal from drifting.

155-178: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test name implies a transition but no transition is exercised.

TestStorageClassSurvivesTopologyTransition only reads the current thin-csi StorageClass once and checks StoragePolicyName is non-empty; it never actually mutates failure domains to trigger a topology transition. Consider either renaming to reflect what it checks (a static invariant) or extending it to actually mutate FDs and re-check across a transition, consistent with the other tests in this file.

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 155 - 178,
`TestStorageClassSurvivesTopologyTransition` currently only verifies a static
`thinCSIStorageClassName` lookup and `StoragePolicyName` value, so either rename
the test to reflect that it checks a StorageClass invariant or update it to
actually exercise a failure-domain/topology change like the other tests in this
file. If you keep the transition behavior, use the existing kube client setup in
`TestStorageClassSurvivesTopologyTransition` to mutate FDs and then re-read the
StorageClass after the transition; otherwise, adjust the test name and
assertions to match the current read-only 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 `@pkg/operator/storageclasscontroller/storageclasscontroller.go`:
- Around line 201-204: The early-return cache check in storageclasscontroller’s
policy lookup is skipping cleanup before force cleanup is considered. Move the
force-cleanup annotation handling in the same flow as the cached-success guard
in the relevant storageclasscontroller method so `forceCleanup` is evaluated
before returning, and only bypass checks when
`len(c.vCenterStoragePolicy[connection.Hostname]) > 0`, there are no pending
orphans, and `forceCleanup` is false.
- Around line 125-129: The stale vCenter cleanup path in storageclasscontroller
should not emit internal hostnames in logs or events. Update the messages in the
code path that removes entries from c.vCenterStoragePolicy, c.backoffStates, and
c.pendingOrphans to use a non-identifying or redacted identifier instead of
host, and apply the same treatment to the StaleVCenterRemoved event so both klog
and c.recorder avoid exposing vCenter hostnames.
- Around line 123-129: The stale-vCenter cleanup in storageclasscontroller.go
only iterates over c.vCenterStoragePolicy, so it can miss hosts that exist only
in c.backoffStates or c.pendingOrphans. Update the cleanup logic around the
host-removal loop to consider all per-vCenter state maps, and when a host is
inactive remove its entries from vCenterStoragePolicy, backoffStates, and
pendingOrphans together so OrphanCleanupPending cannot stay true for a removed
vCenter.

In `@pkg/operator/storageclasscontroller/vmware_test.go`:
- Line 388: The cleanup calls to deleteStoragePolicy in vmware_test are ignoring
returned errors, which violates the error-handling requirement. Update the
affected test cleanup paths to capture and handle the error from
deleteStoragePolicy instead of discarding it, using the apiClient2 and apiClient
references in the test helpers so the cleanup failures are not hidden.
- Around line 357-365: Enable the day-2 cleanup path in this CNS-unavailable
test by setting the storagePolicyAPI’s day2Enabled flag to true in the test
setup so createStoragePolicy exercises orphan cleanup/deletion. Update the test
fixture around apiClient2 and the createStoragePolicy flow to preserve the
existing CNS-unavailable scenario while ensuring the day-2 safety branch is
actually covered.

In `@pkg/operator/storageclasscontroller/vmware.go`:
- Around line 243-249: The orphan-detection path in
storageclasscontroller/vmware.go should block SPBM profile deletion when
`findOrphanedTags` fails. Update the cleanup flow around `findOrphanedTags`,
`detachOrphanTags`, and the deletion branch so an error sets a non-empty
unresolved state or otherwise prevents the delete condition from passing. Make
the same safeguard in the related cleanup block that also uses
`unresolved`/`v.unresolvedOrphans`, ensuring a transient tag lookup error cannot
lead to deleting the policy.
- Around line 264-273: The new logging and events in vmware.go are exposing
internal vCenter/datastore identifiers such as hostnames and
environment-specific names. Update the warning/event paths around
deleteStoragePolicy and the related recorder.Eventf/klog.Warningf calls to
remove or redact those values, using generic wording or placeholder identifiers
instead of real hostnames/datastore names. Apply the same redaction approach
consistently in the other affected log/event blocks referenced by the review so
no internal customer environment details are emitted.
- Around line 266-276: The deletion path in the vmware storage policy cleanup
flow is swallowing failures by always returning an empty name and nil even when
deleteStoragePolicy() errors. Update the logic around deleteStoragePolicy and
the return after the warning so that a failed deletion returns the existing
v.policyName together with the deletion error, while keeping the success path
unchanged. This will let the controller surface the failure and retry with
backoff instead of treating the policy as successfully cleared.

In `@test/e2e/failure_domain_lifecycle_test.go`:
- Around line 403-439: TestVCenterRemovalCleanup is a no-op because it only
detects a multi-vCenter setup and logs, so it never validates stale cleanup
behavior. Update TestVCenterRemovalCleanup to actually mutate the infrastructure
or failure domains and assert the expected cleanup state, reusing the existing
failure-domain/health polling pattern from the other tests (for example, check
for OrphanCleanupPending or operator health after removal). If this scenario
cannot be exercised automatically, change the test to t.Skip with a clear
manual-verification message instead of leaving a passing test with no
assertions.
- Around line 382-401: verifyOrphanConditionPending currently only logs and
never asserts, so the final safety check in TestPVSafetyBlocksCleanup can
silently pass even when the condition never becomes True. Update
verifyOrphanConditionPending to mirror verifyOrphanConditionCleared by treating
a wait.PollUntilContextTimeout timeout as a test failure with t.Errorf or
t.Fatalf, while keeping the success path tied to the
StorageClassControllerOrphanCleanupPending condition lookup in
configClient.ConfigV1().ClusterOperators().Get.
- Around line 211-220: TestPVSafetyBlocksCleanup is creating a plain PVC via
newTestPVC without any consumer, so thin-csi WaitForFirstConsumer may never bind
it and the test can skip instead of exercising orphan cleanup. Update the setup
to create a Pod that mounts/uses the PVC, and if needed add explicit topology
constraints so the claim binds on the intended datastore before waiting. Apply
the same assumption fix in verifyPVCProvisionable so it also provisions
deterministically.
- Around line 359-401: The orphan condition checks in
verifyOrphanConditionCleared and verifyOrphanConditionPending are matching the
wrong condition type string. Update both helpers to use the controller’s actual
condition type, VMwareVSphereDriverStorageClassControllerOrphanCleanupPending,
so the polling logic correctly detects pending and cleared states instead of
silently passing or timing out.

---

Nitpick comments:
In `@test/e2e/failure_domain_lifecycle_test.go`:
- Line 214: The namespace value is duplicated as a string literal in the failure
domain lifecycle tests; extract it into a package-level constant and reuse it
everywhere. Update the references in the test helpers/flows around the ns
assignment so both occurrences use the same constant, preventing the duplicated
literal from drifting.
- Around line 155-178: `TestStorageClassSurvivesTopologyTransition` currently
only verifies a static `thinCSIStorageClassName` lookup and `StoragePolicyName`
value, so either rename the test to reflect that it checks a StorageClass
invariant or update it to actually exercise a failure-domain/topology change
like the other tests in this file. If you keep the transition behavior, use the
existing kube client setup in `TestStorageClassSurvivesTopologyTransition` to
mutate FDs and then re-read the StorageClass after the transition; otherwise,
adjust the test name and assertions to match the current read-only behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Comment thread pkg/operator/storageclasscontroller/storageclasscontroller.go Outdated
Comment on lines +125 to +129
klog.V(2).Infof("Removing stale vCenterStoragePolicy entry for %s", host)
delete(c.vCenterStoragePolicy, host)
delete(c.backoffStates, host)
delete(c.pendingOrphans, host)
c.recorder.Eventf("StaleVCenterRemoved", "Removed stale vCenter entry for %s", host)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Avoid logging vCenter hostnames in logs/events.

These changed log/event messages include internal vCenter hostnames. Prefer a non-identifying message or a stable redacted identifier. As per coding guidelines, “Flag logging that may expose passwords, tokens, API keys, PII … session IDs, internal hostnames, or customer data.”

🤖 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 `@pkg/operator/storageclasscontroller/storageclasscontroller.go` around lines
125 - 129, The stale vCenter cleanup path in storageclasscontroller should not
emit internal hostnames in logs or events. Update the messages in the code path
that removes entries from c.vCenterStoragePolicy, c.backoffStates, and
c.pendingOrphans to use a non-identifying or redacted identifier instead of
host, and apply the same treatment to the StaleVCenterRemoved event so both klog
and c.recorder avoid exposing vCenter hostnames.

Source: Coding guidelines

Comment thread pkg/operator/storageclasscontroller/storageclasscontroller.go Outdated
Comment thread pkg/operator/storageclasscontroller/vmware_test.go
Comment thread pkg/operator/storageclasscontroller/vmware_test.go Outdated
Comment on lines +266 to +276
if delErr := v.deleteStoragePolicy(ctx); delErr != nil {
klog.Warningf("Failed to delete orphaned storage policy %s on vCenter %s: %v",
v.policyName, v.vcenterApiConnection.Hostname, delErr)
} else {
v.policyCreated = false
if v.recorder != nil {
v.recorder.Eventf("StoragePolicyDeleted", "Deleted orphaned SPBM profile %s from vCenter %s",
v.policyName, v.vcenterApiConnection.Hostname)
}
}
return "", nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Propagate SPBM deletion failures.

When deleteStoragePolicy fails, this still returns ("", nil), so the controller records an empty policy and won’t retry with error backoff. Return the existing policy name plus an error on delete failure.

Suggested fix
 		if delErr := v.deleteStoragePolicy(ctx); delErr != nil {
 			klog.Warningf("Failed to delete orphaned storage policy %s on vCenter %s: %v",
 				v.policyName, v.vcenterApiConnection.Hostname, delErr)
+			return v.policyName, fmt.Errorf("failed to delete orphaned storage policy %s: %v", v.policyName, delErr)
 		} else {
 			v.policyCreated = false
 			if v.recorder != nil {
 				v.recorder.Eventf("StoragePolicyDeleted", "Deleted orphaned SPBM profile %s from vCenter %s",
 					v.policyName, v.vcenterApiConnection.Hostname)
 			}
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if delErr := v.deleteStoragePolicy(ctx); delErr != nil {
klog.Warningf("Failed to delete orphaned storage policy %s on vCenter %s: %v",
v.policyName, v.vcenterApiConnection.Hostname, delErr)
} else {
v.policyCreated = false
if v.recorder != nil {
v.recorder.Eventf("StoragePolicyDeleted", "Deleted orphaned SPBM profile %s from vCenter %s",
v.policyName, v.vcenterApiConnection.Hostname)
}
}
return "", nil
if delErr := v.deleteStoragePolicy(ctx); delErr != nil {
klog.Warningf("Failed to delete orphaned storage policy %s on vCenter %s: %v",
v.policyName, v.vcenterApiConnection.Hostname, delErr)
return v.policyName, fmt.Errorf("failed to delete orphaned storage policy %s: %v", v.policyName, delErr)
} else {
v.policyCreated = false
if v.recorder != nil {
v.recorder.Eventf("StoragePolicyDeleted", "Deleted orphaned SPBM profile %s from vCenter %s",
v.policyName, v.vcenterApiConnection.Hostname)
}
}
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 `@pkg/operator/storageclasscontroller/vmware.go` around lines 266 - 276, The
deletion path in the vmware storage policy cleanup flow is swallowing failures
by always returning an empty name and nil even when deleteStoragePolicy()
errors. Update the logic around deleteStoragePolicy and the return after the
warning so that a failed deletion returns the existing v.policyName together
with the deletion error, while keeping the success path unchanged. This will let
the controller surface the failure and retry with backoff instead of treating
the policy as successfully cleared.

Comment on lines +211 to +220
// Create a PVC on the last failure domain's datastore
t.Log("Creating PVC to block orphan cleanup")
pvcName := fmt.Sprintf("pv-safety-test-%d", time.Now().Unix())
ns := "openshift-cluster-csi-drivers"
pvc := newTestPVC(pvcName, ns, thinCSIStorageClassName)

_, err = kubeClient.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{})
if err != nil {
t.Fatalf("Failed to create test PVC: %v", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

What VolumeBindingMode does the vSphere CSI thin-csi StorageClass use in OpenShift, and how does topology-aware provisioning select a failure domain without a consuming pod?

💡 Result:

In OpenShift, the vSphere CSI thin-csi StorageClass uses the WaitForFirstConsumer volume binding mode [1][2][3][4][5]. Regarding your question on how topology-aware provisioning selects a failure domain without a consuming pod: it generally does not [6]. The WaitForFirstConsumer binding mode is specifically designed to delay the creation and binding of a Persistent Volume (PV) until a pod that requires the Persistent Volume Claim (PVC) is scheduled [6][7][8]. This mechanism allows the Kubernetes scheduler to use pod-specific scheduling constraints (such as node affinity or pod affinity) to inform the decision of which failure domain (e.g., zone or region) is most appropriate for provisioning the volume [6][7]. If the volume binding mode is set to Immediate (as opposed to WaitForFirstConsumer), volume provisioning occurs as soon as the PVC is created [6]. In such cases, if topology-aware provisioning is enabled but no pod constraints are available to guide the selection, the system may rely on default behaviors, such as Kubernetes randomly selecting a topology domain from the available options [9]. However, without the context of a pod's requirements, topology-aware provisioning cannot make an "intelligent" choice based on where a workload will actually run [7][8]. If the randomly selected topology domain lacks the necessary resources (such as worker nodes or datastore access), the volume provisioning might fail, or the pod (once created) may remain in a Pending state [9].

Citations:


🏁 Script executed:

sed -n '180,260p' test/e2e/failure_domain_lifecycle_test.go

Repository: openshift/vmware-vsphere-csi-driver-operator

Length of output: 3037


🏁 Script executed:

sed -n '300,370p' test/e2e/failure_domain_lifecycle_test.go

Repository: openshift/vmware-vsphere-csi-driver-operator

Length of output: 2801


Create a consumer Pod before waiting on PVC binding.
newTestPVC() is just a plain PVC, so with thin-csi’s WaitForFirstConsumer binding it won’t bind until something consumes it. That makes TestPVSafetyBlocksCleanup fall into the skip path instead of exercising orphan cleanup, and verifyPVCProvisionable has the same assumption. If the goal is a deterministic datastore, add a Pod and/or explicit topology constraints before waiting on the claim.

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 211 - 220,
TestPVSafetyBlocksCleanup is creating a plain PVC via newTestPVC without any
consumer, so thin-csi WaitForFirstConsumer may never bind it and the test can
skip instead of exercising orphan cleanup. Update the setup to create a Pod that
mounts/uses the PVC, and if needed add explicit topology constraints so the
claim binds on the intended datastore before waiting. Apply the same assumption
fix in verifyPVCProvisionable so it also provisions deterministically.

Comment thread test/e2e/failure_domain_lifecycle_test.go
Comment thread test/e2e/failure_domain_lifecycle_test.go
Comment thread test/e2e/failure_domain_lifecycle_test.go
@jcpowermac jcpowermac force-pushed the multi-vcenter-failure-domain-lifecycle branch from 88c3cdd to 6483f9d Compare July 1, 2026 13:35
@jcpowermac

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@jcpowermac jcpowermac force-pushed the multi-vcenter-failure-domain-lifecycle branch from 6483f9d to 5f140f5 Compare July 1, 2026 14:20
…e gate

Vendor openshift/api@v0.0.0-20260701110937-6e21043ed12a which adds the
VSphereMultiVCenterDay2 feature gate definition. This gate controls
day-2 failure domain lifecycle operations (orphan tag cleanup, SPBM
profile deletion, per-vCenter backoff) in the storage class controller.

Updates both root and openshift-tests go.mod to the same openshift/api
version and re-vendors the workspace.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jcpowermac jcpowermac force-pushed the multi-vcenter-failure-domain-lifecycle branch from 5f140f5 to b8cefb6 Compare July 1, 2026 15:03
@jcpowermac

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jcpowermac

Copy link
Copy Markdown
Author

/test e2e-vsphere-operator-test
/test e2e-vsphere-csi-vcf9

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@pkg/operator/storageclasscontroller/storageclasscontroller_test.go`:
- Around line 276-285: Update the backoff expectations in
storageclasscontroller_test around the sync/backoff test cases so successful
reconciliations assert the 1-minute reset contract instead of a 10-minute delay.
Adjust the relevant cases in the table-driven tests for the controller’s sync
behavior so the success path and the fail-then-succeed sequence both use the
same reset interval, keeping the expectations aligned with the intended
lifecycle behavior.

In `@test/e2e/failure_domain_lifecycle_test.go`:
- Around line 230-232: The polling callbacks in the persistent volume claim
lifecycle tests are swallowing real API failures by returning false, nil after
kubeClient.CoreV1().PersistentVolumeClaims(...).Get errors. Update the affected
polling logic in the PVC checks to return the actual error from Get, and only
treat expected NotFound cases as retryable when appropriate; use the existing
polling closures around the PVC lookup code to preserve RBAC/API failures
instead of masking them as timeouts.
- Around line 100-101: The lifecycle e2e logs are exposing vSphere
topology/storage identifiers, so update the failure-domain removal logs to avoid
printing datacenter, datastore, failure-domain, or storage-policy names. In the
relevant test flow around the failure-domain lifecycle logging, replace
identifier-based messages with generic or count/index-based text unless a
specific value is strictly needed for debugging. Apply the same redaction
pattern to all similar log statements in this test so the output no longer
reveals internal infrastructure details.
- Around line 152-178: `TestStorageClassSurvivesTopologyTransition` currently
only reads the existing `thinCSIStorageClassName` and never drives any
failure-domain changes, so it does not test the intended topology lifecycle.
Update this test to actually exercise a 2→1→2→0 transition by using the
failure-domain/topology helpers already available in the test suite, then verify
the `StorageClass` behavior after each change instead of just logging
`StoragePolicyName`. Keep the check anchored around
`TestStorageClassSurvivesTopologyTransition` and the `thinCSIStorageClassName`
lookup so the test still validates the same storage class across the transition.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e45ae33f-0f07-41a3-807e-4462e311333f

📥 Commits

Reviewing files that changed from the base of the PR and between 88c3cdd and b8cefb6.

⛔ Files ignored due to path filters (225)
  • go.sum is excluded by !**/*.sum
  • go.work.sum is excluded by !**/*.sum
  • openshift-tests/go.sum is excluded by !**/*.sum
  • vendor/github.com/openshift/api/.ci-operator.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/.golangci.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/Dockerfile.ocp is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/Makefile is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apiextensions/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apiextensions/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/apiserver/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apiserver/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/apps/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apps/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apps/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/authorization/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/authorization/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/authorization/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/build/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/build/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/build/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/cloudnetwork/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/cloudnetwork/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/cloudnetwork/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_apiserver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_authentication.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_cluster_operator.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_cluster_version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_crio_credential_provider_config.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_dns.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_image.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_infrastructure.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_kmsencryption.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_network.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/types_tlssecurityprofile.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1alpha1/types_cluster_monitoring.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1alpha1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1alpha2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1alpha2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/console/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/console/v1/types_console_plugin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/console/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/console/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/envtest-releases.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/install.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1/Makefile is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1/types_pacemakercluster.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/etcd/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/etcd/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/etcd/v1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/etcd/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1alpha1/types_pacemakercluster.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/etcd/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/etcd/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/features.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/features/features.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/helm/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/helm/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/image/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/image/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/image/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/imageregistry/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/imageregistry/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/install.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/kubecontrolplane/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/kubecontrolplane/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/legacyconfig/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/legacyconfig/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/machine/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/machine/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/machine/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/machine/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/machine/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/machine/v1beta1/types_machineset.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/machine/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/machine/v1beta1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/monitoring/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/monitoring/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/network/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/network/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/network/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/network/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/network/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/networkoperator/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/networkoperator/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/networkoperator/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/oauth/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/oauth/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/oauth/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/openshiftcontrolplane/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/openshiftcontrolplane/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_authentication.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_csi_cluster_driver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_etcd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_ingresscontroller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_kmsencryption.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_kubeapiserver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/types_openshiftapiserver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1alpha1/types_clusterapi.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1alpha1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operatorcontrolplane/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operatorcontrolplane/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/osin/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/osin/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/project/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/project/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/project/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/quota/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/quota/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/quota/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/quota/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/quota/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/quota/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/route/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/route/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/route/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/samples/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/samples/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/samples/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/security/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/security/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/security/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/security/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/security/v1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/security/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/security/v1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/servicecertsigner/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/servicecertsigner/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/sharedresource/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/sharedresource/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/template/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/template/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/template/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/user/.codegen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/user/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/user/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/kube-openapi/pkg/builder/openapi.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/builder3/openapi.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/serialization.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/alias.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal_any.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal_default.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal_funcs.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal_inlined.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal_methods.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/arshal_time.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/decode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/encode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/fields.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/fold.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/intern.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/internal/internal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/internal/jsonflags/flags.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/internal/jsonopts/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/internal/jsonwire/decode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/internal/jsonwire/encode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/internal/jsonwire/wire.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/alias.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/decode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/encode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/export.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/pools.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/quote.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/state.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/token.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/jsontext/value.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/migrate.sh is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/value.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/schemaconv/openapi.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/schemaconv/proto_models.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/encoding.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/example.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/external_documentation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/header.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/media_type.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/operation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/parameter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/path.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/request_body.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/response.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/security_scheme.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/server.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/spec3/spec.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/header.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/info.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/items.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/operation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/parameter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/path_item.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/paths.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/response.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/responses.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/schema.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/security_scheme.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/swagger.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/kube-openapi/pkg/validation/spec/tag.go is excluded by !**/vendor/**, !vendor/**
  • vendor/modules.txt is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (10)
  • go.mod
  • openshift-tests/go.mod
  • pkg/operator/storageclasscontroller/storageclasscontroller.go
  • pkg/operator/storageclasscontroller/storageclasscontroller_test.go
  • pkg/operator/storageclasscontroller/vmware.go
  • pkg/operator/storageclasscontroller/vmware_test.go
  • pkg/operator/utils/metric.go
  • pkg/operator/vspherecontroller/driver_starter_test.go
  • pkg/operator/vspherecontroller/vspherecontroller.go
  • test/e2e/failure_domain_lifecycle_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • go.mod
  • pkg/operator/storageclasscontroller/vmware_test.go
  • pkg/operator/utils/metric.go
  • pkg/operator/vspherecontroller/vspherecontroller.go
  • pkg/operator/vspherecontroller/driver_starter_test.go
  • pkg/operator/storageclasscontroller/vmware.go
  • pkg/operator/storageclasscontroller/storageclasscontroller.go

Comment on lines +276 to +285
name: "success uses 10-minute interval",
syncSequence: []bool{true, true, true},
// Success always uses successBackoff.Duration (10 min)
expectedDurations: []time.Duration{10 * time.Minute, 10 * time.Minute, 10 * time.Minute},
},
{
name: "fail 3x then succeed resets error backoff",
syncSequence: []bool{false, false, false, true, false},
// fail: 1m, fail: 2m, fail: 4m, success: 10m, fail: 1m (error backoff reset by success)
expectedDurations: []time.Duration{time.Minute, 2 * time.Minute, 4 * time.Minute, 10 * time.Minute, time.Minute},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Align success-backoff assertions with the 1-minute reset contract.

These tests encode successful sync as a 10-minute delay, but the PR objective says success should reset to a 1-minute interval. As written, the tests will preserve the wrong lifecycle behavior.

Proposed test contract fix
-			name:         "success uses 10-minute interval",
+			name:         "success uses 1-minute interval",
 			syncSequence: []bool{true, true, true},
-			// Success always uses successBackoff.Duration (10 min)
-			expectedDurations: []time.Duration{10 * time.Minute, 10 * time.Minute, 10 * time.Minute},
+			// Success resets to the normal 1-minute interval.
+			expectedDurations: []time.Duration{time.Minute, time.Minute, time.Minute},
@@
-			// fail: 1m, fail: 2m, fail: 4m, success: 10m, fail: 1m (error backoff reset by success)
-			expectedDurations: []time.Duration{time.Minute, 2 * time.Minute, 4 * time.Minute, 10 * time.Minute, time.Minute},
+			// fail: 1m, fail: 2m, fail: 4m, success: 1m, fail: 1m (error backoff reset by success)
+			expectedDurations: []time.Duration{time.Minute, 2 * time.Minute, 4 * time.Minute, time.Minute, time.Minute},
@@
-	// vcenter-b should have success interval (10m)
+	// vcenter-b should have success interval (1m)
 	bDelay := bsB.nextCheck.Sub(beforeB)
-	tolerance := float64(successCheckInterval) * 0.05
-	if bDelay < successCheckInterval-time.Duration(tolerance) || bDelay > successCheckInterval+time.Duration(tolerance) {
-		t.Errorf("vcenter-b: expected success interval ~%v, got %v", successCheckInterval, bDelay)
+	expectedSuccessDelay := time.Minute
+	tolerance := float64(expectedSuccessDelay) * 0.05
+	if bDelay < expectedSuccessDelay-time.Duration(tolerance) || bDelay > expectedSuccessDelay+time.Duration(tolerance) {
+		t.Errorf("vcenter-b: expected success interval ~%v, got %v", expectedSuccessDelay, bDelay)
 	}

Also applies to: 383-387

🤖 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 `@pkg/operator/storageclasscontroller/storageclasscontroller_test.go` around
lines 276 - 285, Update the backoff expectations in storageclasscontroller_test
around the sync/backoff test cases so successful reconciliations assert the
1-minute reset contract instead of a 10-minute delay. Adjust the relevant cases
in the table-driven tests for the controller’s sync behavior so the success path
and the fail-then-succeed sequence both use the same reset interval, keeping the
expectations aligned with the intended lifecycle behavior.

Comment on lines +100 to +101
t.Logf("Removing failure domain %q (datacenter=%s, datastore=%s)",
removedFD.Name, removedFD.Topology.Datacenter, removedFD.Topology.Datastore)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid logging vSphere topology and storage-policy identifiers.

Datacenter, datastore, failure-domain, and storage-policy names can expose customer/internal infrastructure details in e2e logs. Prefer count/index-based logs unless the identifier is required for debugging. As per coding guidelines, “Flag logging that may expose passwords, tokens, API keys, PII ..., internal hostnames, or customer data.”

Proposed log redaction
-	t.Logf("Removing failure domain %q (datacenter=%s, datastore=%s)",
-		removedFD.Name, removedFD.Topology.Datacenter, removedFD.Topology.Datastore)
+	t.Logf("Removing one failure domain from %d configured failure domains", len(fds))
@@
-	t.Logf("StorageClass %s still exists with policy %s", sc.Name, sc.Parameters["StoragePolicyName"])
+	t.Logf("StorageClass %s still exists with a storage policy configured", sc.Name)
@@
-		t.Logf("StorageClass thin-csi has StoragePolicyName=%s", policyName)
+		t.Log("StorageClass thin-csi has a StoragePolicyName configured")

Also applies to: 142-142, 176-176

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 100 - 101, The
lifecycle e2e logs are exposing vSphere topology/storage identifiers, so update
the failure-domain removal logs to avoid printing datacenter, datastore,
failure-domain, or storage-policy names. In the relevant test flow around the
failure-domain lifecycle logging, replace identifier-based messages with generic
or count/index-based text unless a specific value is strictly needed for
debugging. Apply the same redaction pattern to all similar log statements in
this test so the output no longer reveals internal infrastructure details.

Source: Coding guidelines

Comment on lines +152 to +178
// TestStorageClassSurvivesTopologyTransition verifies that the thin-csi
// StorageClass survives transitions between topology-enabled and
// topology-disabled states when failure domains change.
func TestStorageClassSurvivesTopologyTransition(t *testing.T) {
kubeConfig, err := library.NewClientConfigForTest(t)
if err != nil {
t.Fatalf("Failed to get kubeconfig: %v", err)
}

kubeClient := kubernetes.NewForConfigOrDie(rest.AddUserAgent(kubeConfig, clientName))
ctx := context.TODO()

sc, err := kubeClient.StorageV1().StorageClasses().Get(ctx, thinCSIStorageClassName, metav1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
t.Skip("StorageClass thin-csi not found, skipping topology transition test")
}
t.Fatalf("Failed to get StorageClass: %v", err)
}

policyName := sc.Parameters["StoragePolicyName"]
if policyName == "" {
t.Error("StorageClass thin-csi has empty StoragePolicyName")
} else {
t.Logf("StorageClass thin-csi has StoragePolicyName=%s", policyName)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make this test exercise an actual topology transition.

TestStorageClassSurvivesTopologyTransition only checks the current thin-csi StorageClass. It never changes failure domains, so it does not verify the PR’s stated 2→1→2→0 topology lifecycle behavior.

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 152 - 178,
`TestStorageClassSurvivesTopologyTransition` currently only reads the existing
`thinCSIStorageClassName` and never drives any failure-domain changes, so it
does not test the intended topology lifecycle. Update this test to actually
exercise a 2→1→2→0 transition by using the failure-domain/topology helpers
already available in the test suite, then verify the `StorageClass` behavior
after each change instead of just logging `StoragePolicyName`. Keep the check
anchored around `TestStorageClassSurvivesTopologyTransition` and the
`thinCSIStorageClassName` lookup so the test still validates the same storage
class across the transition.

Comment on lines +230 to +232
p, err := kubeClient.CoreV1().PersistentVolumeClaims(ns).Get(pollCtx, pvcName, metav1.GetOptions{})
if err != nil {
return false, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not swallow API errors inside polling callbacks.

Returning (false, nil) on a non-nil Get error masks RBAC/API failures as generic timeouts. Return the error, or only suppress expected NotFound cases. As per path instructions, “Never ignore error returns.”

Proposed polling error handling
 		p, err := kubeClient.CoreV1().PersistentVolumeClaims(ns).Get(pollCtx, pvcName, metav1.GetOptions{})
 		if err != nil {
-			return false, nil
+			if apierrors.IsNotFound(err) {
+				return false, nil
+			}
+			return false, err
 		}
@@
 		co, err := configClient.ConfigV1().ClusterOperators().Get(pollCtx, storageCOName, metav1.GetOptions{})
 		if err != nil {
-			return false, nil
+			return false, err
 		}
@@
 		p, err := kubeClient.CoreV1().PersistentVolumeClaims(ns).Get(pollCtx, pvcName, metav1.GetOptions{})
 		if err != nil {
-			return false, nil
+			if apierrors.IsNotFound(err) {
+				return false, nil
+			}
+			return false, err
 		}
@@
 		co, err := configClient.ConfigV1().ClusterOperators().Get(pollCtx, storageCOName, metav1.GetOptions{})
 		if err != nil {
-			return false, nil
+			return false, err
 		}
@@
 		co, err := configClient.ConfigV1().ClusterOperators().Get(pollCtx, storageCOName, metav1.GetOptions{})
 		if err != nil {
-			return false, nil
+			return false, err
 		}

Also applies to: 293-295, 327-329, 363-365, 385-387

🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 232-232: error is not nil (line 230) but it returns nil

(nilerr)

🤖 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 `@test/e2e/failure_domain_lifecycle_test.go` around lines 230 - 232, The
polling callbacks in the persistent volume claim lifecycle tests are swallowing
real API failures by returning false, nil after
kubeClient.CoreV1().PersistentVolumeClaims(...).Get errors. Update the affected
polling logic in the PVC checks to return the actual error from Get, and only
treat expected NotFound cases as retryable when appropriate; use the existing
polling closures around the PVC lookup code to preserve RBAC/API failures
instead of masking them as timeouts.

Sources: Path instructions, Linters/SAST tools

…Day2

Adds day-2 failure domain removal awareness to the storage class
controller, gated behind the VSphereMultiVCenterDay2 TechPreview feature
gate. When a failure domain is removed from the Infrastructure spec, the
operator detects orphaned tags, safely detaches them (blocking on CNS PV
presence), and deletes the SPBM profile when the vCenter has zero
remaining failure domains.

Key changes:

- Gate all day-2 lifecycle paths (orphan detection, tag detach, SPBM
  deletion, stale host cleanup) on VSphereMultiVCenterDay2 feature gate
- Per-vCenter exponential backoff with independent state tracking,
  success resets error backoff to default interval
- Orphan tag detection compares tagged datastores against current failure
  domains using (datacenter, datastore) tuples to prevent cross-DC
  false matches
- CNS PV safety: query CNS volumes per datastore before detaching tags,
  conservative fallback (skip detach) when CNS API is unavailable
- Force-cleanup annotation on ClusterCSIDriver to bypass PV safety check
- OrphanCleanupPending informational condition on operator status with
  pending orphan counts, cleared when resolved
- Stale vCenter cleanup iterates union of all per-vCenter maps to
  prevent leaked state
- Early-return cache bypassed when forceCleanup is set or pendingOrphans
  exist, preventing stale condition state
- findOrphanedTags errors block SPBM deletion to prevent unsafe cleanup
  on transient API failures
- SPBM delete failure retains known policy name instead of returning
  empty string
- Prometheus metrics for tag operations and orphan detection
- E2e tests for FD removal tag cleanup, PV safety blocking, topology
  transition, and operator health verification
- Unit tests for per-vCenter backoff, gate-off behavior, pending orphan
  resync bypass, and stale map cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jcpowermac jcpowermac force-pushed the multi-vcenter-failure-domain-lifecycle branch from b8cefb6 to acb68c3 Compare July 1, 2026 17:21
@jcpowermac

Copy link
Copy Markdown
Author

/test e2e-vsphere-operator-test
/test e2e-vsphere-csi-vcf9

@jcpowermac

Copy link
Copy Markdown
Author

/test e2e-vsphere-operator-test

@openshift-ci

openshift-ci Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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

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

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant