Skip to content

fix(csi): route RWX Block volumes to upstream hotplug detach - #2658

Merged
Aleksei Sviridkin (lexfrei) merged 2 commits into
mainfrom
fix/kubevirt-csi-rwx-block-unpublish
May 25, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 2 commits into
mainfrom
fix/kubevirt-csi-rwx-block-unpublish

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented May 15, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Fixes routing in WrappedControllerService so RWX Block volumes (KubeVirt live-migration disks) are no longer treated as NFS during unpublish and expand.

Root cause

ControllerUnpublishVolume selected the NFS cleanup branch based on hasRWXAccessMode(pvc) alone. RWX Block matched and went down the NFS path — which just deletes a CiliumNetworkPolicy. For a block volume that CNP does not exist, so removeCNPOwnerReference returns successfully without doing anything, and the function reports success without ever calling upstream's hotplug detach. The DataVolume entry stays in VM.spec.template.spec.volumes permanently. When the same PVC later attaches to a different VM, linstor-csi's anti-split-brain check sees pods from two different VMs referencing the same RWX block PVC and blocks the attach with:

RWX block volume <ns>/<pvc> is being used by pods from different VMs (<vm-a> and <vm-b>);
this is not supported - RWX block volumes with allow-two-primaries are only for live migration of a single VM

ControllerPublishVolume was unaffected because it routes on the explicit nfsVolumeKey from VolumeContext. ControllerUnpublishVolume does not receive VolumeContext and has to infer from the PVC, and the previous inference was incomplete.

Fix

isNFSVolume(pvc) now requires ReadWriteMany access mode and VolumeMode == Filesystem (always set by our NFS CreateVolume path). RWX Block correctly delegates to upstream ControllerUnpublishVolume, which removes the entry from VM spec via RemoveVolumeFromVM. The same predicate replaces the previous access-mode check in ControllerExpandVolume, which was also clearing NodeExpansionRequired for RWX Block — block volumes still need in-VM disk expansion.

Fixes #2634.

Release note

fix(csi): route RWX Block volumes in kubevirt-csi-driver to upstream hotplug detach so VM spec entries are cleaned up on unpublish

Summary by CodeRabbit

  • Bug Fixes

    • More accurate detection of network-attached (NFS) vs block volumes, preventing incorrect cleanup of block volumes.
    • NFS volumes continue to receive special cleanup; non-NFS volumes now follow upstream cleanup paths.
  • Refactor

    • Node-side expansion logic refined to base decisions on improved NFS classification (RWX + filesystem), reducing expansion errors.

Review Change Stack

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request improves the CSI driver's volume handling logic to correctly identify and process RWX Block volumes. Previously, these volumes were incorrectly categorized as NFS, leading to improper cleanup during unpublish operations and incorrect expansion configurations. By refining the volume identification criteria, the driver now ensures proper lifecycle management for live-migration disks, preventing potential attachment conflicts.

Highlights

  • Volume Routing Logic: Updated the WrappedControllerService to correctly distinguish between NFS (RWX Filesystem) and RWX Block volumes by introducing an isNFSVolume helper.
  • Unpublish Behavior: Ensured RWX Block volumes are delegated to upstream hotplug removal, preventing stale VM spec entries that previously blocked re-attachment.
  • Expansion Logic: Adjusted ControllerExpandVolume to ensure RWX Block volumes correctly retain node-side expansion requirements.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug size/S This PR changes 10-29 lines, ignoring generated files labels May 15, 2026
@coderabbitai

coderabbitai Bot commented May 15, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR adds an isNFSVolume(pvc) helper that classifies PVCs as NFS only when they are RWX and Filesystem (not Block), and updates ControllerUnpublishVolume and ControllerExpandVolume to use this predicate for cleanup and node-expansion decisions.

Changes

NFS Volume Classification

Layer / File(s) Summary
isNFSVolume helper predicate
packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go
Adds isNFSVolume(pvc) which returns true for PVCs with RWX access mode and either unset or PersistentVolumeFilesystem VolumeMode; returns false for PersistentVolumeBlock.
ControllerUnpublishVolume and ControllerExpandVolume NFS classification
packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go
ControllerUnpublishVolume now uses isNFSVolume(pvc) to choose between NFS cleanup (remove VMI ownerReference from CiliumNetworkPolicy) and delegating to the upstream unpublish implementation; inline comment clarifies RWX Block/live-migration volumes are excluded. ControllerExpandVolume sets NodeExpansionRequired=false only when isNFSVolume(pvc) is true (RWX + Filesystem).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nibble on predicates by moonlight,
RWX and Filesystem set things right,
Block hops away where live-migrators play,
A tidy helper keeps mix-ups at bay,
Hooray for clearer cleanup tonight. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title precisely describes the main fix: routing RWX Block volumes to upstream hotplug detach instead of treating them as NFS, which directly addresses the core issue.
Linked Issues check ✅ Passed The code changes fully implement the requirements from issue #2634: the new isNFSVolume predicate ensures ControllerUnpublishVolume now calls upstream RemoveVolume for RWX Block volumes, guaranteeing VM spec cleanup regardless of VMI volumeStatus.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the RWX Block volume routing issue; the helper function and two method updates align with the stated objectives without introducing unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/kubevirt-csi-rwx-block-unpublish

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

❤️ Share

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refines the identification of NFS volumes within the KubeVirt CSI driver by introducing the isNFSVolume function. This helper ensures that RWX Block volumes, used for live migration, are correctly handled via the upstream hotplug removal path rather than being treated as NFS, which prevents issues with stale VM-spec entries. Feedback suggests making the isNFSVolume check more robust by explicitly verifying the Filesystem volume mode to accommodate potential future Kubernetes updates.

Comment on lines +554 to +562
func isNFSVolume(pvc *corev1.PersistentVolumeClaim) bool {
if !hasRWXAccessMode(pvc) {
return false
}
if pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == corev1.PersistentVolumeBlock {
return false
}
return true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low

The current implementation of isNFSVolume treats any non-Block RWX volume as an NFS volume. While Kubernetes currently only supports Filesystem and Block modes, it's more robust to explicitly check for Filesystem (or nil, which defaults to Filesystem) to ensure that any future volume modes aren't accidentally routed to the NFS path.

func isNFSVolume(pvc *corev1.PersistentVolumeClaim) bool {
	if !hasRWXAccessMode(pvc) {
		return false
	}
	return pvc.Spec.VolumeMode == nil || *pvc.Spec.VolumeMode == corev1.PersistentVolumeFilesystem
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — switched isNFSVolume to the explicit Filesystem-only form in c0db805. The predicate now returns true only when VolumeMode is nil (defaults to Filesystem) or Filesystem, so any future VolumeMode value won't accidentally fall through to the NFS path.

@dosubot dosubot Bot added area/kubernetes Issues or PRs related to the tenant Kubernetes app area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) labels May 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go (1)

374-383: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid early success on missing PVC; it can skip required hotplug detach.

If PVC lookup returns NotFound (Line 377), the method exits before the upstream ControllerUnpublishVolume call (Line 383). For RWX Block, this can still leave stale VM spec volume entries when PVC is already gone, which is exactly the attach-blocking failure mode this PR is fixing.

Proposed fix
  pvc, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, dvName, metav1.GetOptions{})
  if err != nil {
    if errors.IsNotFound(err) {
-     return &csi.ControllerUnpublishVolumeResponse{}, nil
+     // PVC may already be deleted, but VM-spec hotplug cleanup may still be required.
+     resp, upErr := w.ControllerService.ControllerUnpublishVolume(ctx, req)
+     if upErr != nil {
+       return nil, upErr
+     }
+     return resp, nil
    }
    return nil, err
  }

  if !isNFSVolume(pvc) {
    return w.ControllerService.ControllerUnpublishVolume(ctx, req)
  }
🤖 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 `@packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go` around
lines 374 - 383, The code currently returns early with success when the PVC Get
returns NotFound, skipping the upstream cleanup; change this so a NotFound error
does not short-circuit the flow: when
infraClient.CoreV1().PersistentVolumeClaims(...).Get returns a NotFound, do not
return immediately—mark pvc as missing (or set a pvcNotFound flag) and fall
through to the existing logic that decides whether to call
w.ControllerService.ControllerUnpublishVolume; specifically ensure the branch
around isNFSVolume(pvc) and the call to
w.ControllerService.ControllerUnpublishVolume(ctx, req) still executes for the
cases that need hotplug detach (e.g., RWX Block) even if pvc is missing, while
preserving the original behavior for NFS volumes.
🤖 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.

Outside diff comments:
In `@packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go`:
- Around line 374-383: The code currently returns early with success when the
PVC Get returns NotFound, skipping the upstream cleanup; change this so a
NotFound error does not short-circuit the flow: when
infraClient.CoreV1().PersistentVolumeClaims(...).Get returns a NotFound, do not
return immediately—mark pvc as missing (or set a pvcNotFound flag) and fall
through to the existing logic that decides whether to call
w.ControllerService.ControllerUnpublishVolume; specifically ensure the branch
around isNFSVolume(pvc) and the call to
w.ControllerService.ControllerUnpublishVolume(ctx, req) still executes for the
cases that need hotplug detach (e.g., RWX Block) even if pvc is missing, while
preserving the original behavior for NFS volumes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 31b46753-31cc-4e5f-9da5-4da2d8d7f9c0

📥 Commits

Reviewing files that changed from the base of the PR and between e4f9a18 and 2f57b1c.

📒 Files selected for processing (1)
  • packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go

WrappedControllerService.ControllerUnpublishVolume decided between NFS
cleanup and upstream hotplug detach using only hasRWXAccessMode(pvc).
RWX Block volumes (KubeVirt live-migration disks) match that check and
were routed to the NFS path, which only deletes a CiliumNetworkPolicy.
For a block volume that CNP does not exist, so removeCNPOwnerReference
returns successfully without doing anything, and the function reports
success without ever calling upstream's hotplug detach. The DataVolume
entry stays in VM.spec.template.spec.volumes; when the same PVC later
attaches to a different VM, linstor-csi's anti-split-brain check sees
pods from two different VMs referencing the same RWX block PVC and
rejects the attach permanently.

Introduce isNFSVolume(pvc) requiring both ReadWriteMany access mode and
VolumeMode=Filesystem (always true for PVCs provisioned via our NFS
CreateVolume path). RWX Block now delegates to upstream, which removes
the entry from VM spec via RemoveVolumeFromVM. The same predicate
replaces the access-mode check in ControllerExpandVolume - RWX Block
volumes still need in-VM disk expansion.

Fixes #2634

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
Address review feedback from gemini-code-assist on
packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go:562:
classify a PVC as NFS only when its VolumeMode is Filesystem (or nil,
which defaults to Filesystem), instead of "anything not Block". Future
VolumeMode values will no longer be silently routed to the NFS path.

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@myasnikovdaniil
myasnikovdaniil force-pushed the fix/kubevirt-csi-rwx-block-unpublish branch from 2f57b1c to c0db805 Compare May 21, 2026 18:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go (1)

374-383: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't ack unpublish when PVC lookup misses.

If the PVC is already gone, this still returns success before the upstream hotplug detach runs. That reintroduces the stale VM.spec.template.spec.volumes case from issue #2634 on any path where publish updated the VM spec but the PVC/DataVolume lookup no longer succeeds. Please fall back to w.ControllerService.ControllerUnpublishVolume here instead of returning an empty success response.

Suggested fix
 	pvc, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, dvName, metav1.GetOptions{})
 	if err != nil {
 		if errors.IsNotFound(err) {
-			return &csi.ControllerUnpublishVolumeResponse{}, nil
+			return w.ControllerService.ControllerUnpublishVolume(ctx, req)
 		}
 		return nil, 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 `@packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go` around
lines 374 - 383, The current PVC Get error handling returns an empty success
when the PVC is not found, which hides failures and reintroduces stale VM spec
issues; change the branch that handles errors.IsNotFound(err) to delegate to the
upstream unpublish implementation by calling and returning
w.ControllerService.ControllerUnpublishVolume(ctx, req) instead of returning
&csi.ControllerUnpublishVolumeResponse{} so that the normal unpublish path runs;
ensure this change is applied in the code that does pvc, err :=
w.infraClient.CoreV1().PersistentVolumeClaims(...).Get(...) and keep the
existing non-NFS check that calls
w.ControllerService.ControllerUnpublishVolume(ctx, req) for non-NFS volumes.
🤖 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.

Outside diff comments:
In `@packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go`:
- Around line 374-383: The current PVC Get error handling returns an empty
success when the PVC is not found, which hides failures and reintroduces stale
VM spec issues; change the branch that handles errors.IsNotFound(err) to
delegate to the upstream unpublish implementation by calling and returning
w.ControllerService.ControllerUnpublishVolume(ctx, req) instead of returning
&csi.ControllerUnpublishVolumeResponse{} so that the normal unpublish path runs;
ensure this change is applied in the code that does pvc, err :=
w.infraClient.CoreV1().PersistentVolumeClaims(...).Get(...) and keep the
existing non-NFS check that calls
w.ControllerService.ControllerUnpublishVolume(ctx, req) for non-NFS volumes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00de61da-f4a5-4ff2-be8f-e932c1a0d00a

📥 Commits

Reviewing files that changed from the base of the PR and between 2f57b1c and c0db805.

📒 Files selected for processing (1)
  • packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — routing predicate now correctly distinguishes our RWX-Filesystem NFS volumes from upstream RWX-Block hotplug volumes; the latter delegates to upstream ControllerUnpublishVolume/ControllerExpandVolume so VM-spec entries get cleaned up and Block disks keep their node-side expansion path. Gemini's VolumeMode==Filesystem tightening landed in c0db805.

Business context: RWX Block volumes for KubeVirt live-migration were silently routed through the NFS cleanup branch (hasRWXAccessMode-only predicate), leaving the DataVolume entry in VM.spec.template.spec.volumes after unpublish; on the next attach to a different VM, the linstor-csi anti-split-brain check then permanently blocks the operation.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 60357eb into main May 25, 2026
13 of 14 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/kubevirt-csi-rwx-block-unpublish branch May 25, 2026 13:58
@myasnikovdaniil myasnikovdaniil added the kind/backport Categorizes issue or PR as requiring a backport to the current release line label May 26, 2026
@github-actions

Copy link
Copy Markdown

Successfully created backport PR for release-1.4:

myasnikovdaniil added a commit that referenced this pull request May 27, 2026
…hotplug detach (#2749)

# Description
Backport of #2658 to `release-1.4`.
@lexfrei Aleksei Sviridkin (lexfrei) removed the area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review label Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kubernetes Issues or PRs related to the tenant Kubernetes app area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) kind/backport Categorizes issue or PR as requiring a backport to the current release line kind/bug Categorizes issue or PR as related to a bug size/S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kubevirt-csi-driver: stale DataVolume in VM spec after rate-limited ControllerPublishVolume causes permanent attach failure

2 participants