Repository navigation
Check VMI status when detaching, not just VM spec - #184
kubevirt-prow[bot] merged 1 commit into
Conversation
|
Hi @mattia-eleuteri. Thanks for your PR. PRs from untrusted users cannot be marked as trusted with I understand the commands that are listed here. DetailsInstructions 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. |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Reopening to retrigger DCO check (the check didn't fire on initial open, likely a webhook race). |
ControllerUnpublishVolume used to return success as soon as the parent
VirtualMachine was missing, on the assumption that a missing VM meant
the hot-plug was already gone. In practice the VMI (and its hot-plug
pod) can outlive the parent VM during a CAPI/CAPK teardown race, so the
infra-side storage device stays exclusively attached to the source host.
The next attempt to attach the same volume to a different VM then fails
with 'Multi-Attach error' from the attach/detach controller or, on
storage backends that enforce exclusive attachment at the device layer
(e.g. DRBD), with 'failed to set source device readwrite'.
The same divergence happens when virt-api updates VM.spec optimistically
on a removevolume call but virt-handler has not finished unplugging the
QEMU device yet — VM.spec no longer references the volume, but the VMI
status still does.
This commit makes ControllerUnpublishVolume consult both surfaces:
- EnsureVolumeRemovedVMI (new) reports whether the volume is still in
VMI.Status.VolumeStatus as a HotplugVolume.
- The new volumeStillAttached helper combines the existing VM-spec
check with the new VMI-status check and only short-circuits to
success when both agree the volume is gone.
removeVolumeFromVm is refactored to fall through to RemoveVolumeFromVMI
when the parent VM is gone but the VMI still reports the hot-plug,
closing the orphan path end-to-end.
Refs: kubevirt#83, kubevirt#119, kubevirt#134.
Signed-off-by: Mattia Eleuteri <[email protected]>
a2332e3 to
dda9996
Compare
kvaps
left a comment
There was a problem hiding this comment.
The diagnosis is correct: ControllerUnpublishVolume currently short-circuits to success as soon as the parent VM is gone (and even returns nil, nil there), so an orphan VMI keeps its hot-plug pod alive and the infra device stays attached to the source host. Checking both surfaces (VM.spec via EnsureVolumeRemovedVM and VMI.status.volumeStatus via the new EnsureVolumeRemovedVMI) and falling through to RemoveVolumeFromVMI when the VM is missing is the right fix, and the final EnsureVolumeRemoved wait was already VMI-aware, so the detach semantics stay consistent end-to-end.
One behavioural note, not a blocker: during a normal unplug, after the volume is gone from VM.spec but before virt-handler finishes the unplug, a retried unpublish now goes down the VMI branch and calls the removevolume subresource against a volume that is already mid-unplug. The subresource will likely reject that, the backoff loop swallows the error and the external-attacher retries, so it converges — just expect some extra log noise compared to the old (incorrectly optimistic) fast path.
Not regenerating mocked_client.go seems reasonable given the file is already stale relative to the interface; a separate mock-regen PR is the right place for that.
LGTM overall — we hit this exact failure mode with DRBD-backed storage, where the leaked hot-plug pod keeps the device exclusively attached and every subsequent attach fails with failed to set source device readwrite.
|
@mattia-eleuteri: The following test failed, say
DetailsInstructions 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. |
|
Pull requests that are marked with After that period the bot marks them with the label /label needs-approver-review |
) ## What this PR does During node reprovisioning in tenant Kubernetes clusters, the VMI (and its hot-plug pod) can outlive the parent VirtualMachine. The upstream kubevirt-csi-driver treats a missing VM as "detach succeeded", so the hot-plug pod stays alive and keeps the infra storage device exclusively attached to the source host — every subsequent attach of that volume fails, on DRBD with `failed to set source device readwrite`. This was hit repeatedly in production tenant clusters (see kubevirt/csi-driver#182). This PR: - updates the pinned `kubevirt.io/csi-driver` module to the latest upstream commit; - backports kubevirt/csi-driver#184 into the controller wrapper: after the upstream unpublish returns success, the wrapper re-checks `VMI.status.volumeStatus` and, if the hot-plug is still reported there (e.g. because the parent VM is already gone), detaches it from the VMI directly via the `removevolume` subresource and waits until it disappears. The backport is self-contained in the wrapper and can be dropped once kubevirt/csi-driver#184 merges upstream and the module pin is bumped past it. Credit to @mattia-eleuteri for the root-cause analysis and the upstream fix. ### Screenshots Not a UI change. ### Release note ```release-note fix(csi): tenant CSI driver no longer leaves orphan hot-plug volumes exclusively attached to the source host when the VMI outlives its deleted VirtualMachine during node reprovisioning ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Enhanced hotplug volume unpublishing with automatic verification to ensure volumes are fully detached before completion, including retry logic with up to 2-minute timeout. * **Dependencies** * Updated KubeVirt API and CSI driver dependencies to latest versions. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Upstream treats a missing parent VM as detach success, so when the VMI outlives its VM during node reprovisioning the hot-plug pod keeps the infra device exclusively attached to the source host and subsequent attaches fail. Re-check VMI volume status after upstream unpublish and detach from the VMI directly. Backport of kubevirt/csi-driver#184. Co-Authored-By: Claude <[email protected]> Signed-off-by: Andrei Kvapil <[email protected]>
|
/retest |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: awels The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
/remove-label needs-approver-review |
ControllerUnpublishVolume used to return success as soon as the parent
VirtualMachine was missing, on the assumption that a missing VM meant
the hot-plug was already gone. In practice the VMI (and its hot-plug
pod) can outlive the parent VM during a CAPI/CAPK teardown race, so the
infra-side storage device stays exclusively attached to the source host.
The next attempt to attach the same volume to a different VM then fails
with 'Multi-Attach error' from the attach/detach controller or, on
storage backends that enforce exclusive attachment at the device layer
(e.g. DRBD), with 'failed to set source device readwrite'.
The same divergence happens when virt-api updates VM.spec optimistically
on a removevolume call but virt-handler has not finished unplugging the
QEMU device yet — VM.spec no longer references the volume, but the VMI
status still does.
This commit makes ControllerUnpublishVolume consult both surfaces:
- EnsureVolumeRemovedVMI (new) reports whether the volume is still in
VMI.Status.VolumeStatus as a HotplugVolume.
- The new volumeStillAttached helper combines the existing VM-spec
check with the new VMI-status check and only short-circuits to
success when both agree the volume is gone.
removeVolumeFromVm is refactored to fall through to RemoveVolumeFromVMI
when the parent VM is gone but the VMI still reports the hot-plug,
closing the orphan path end-to-end.
Refs: kubevirt#83, kubevirt#119, kubevirt#134.
Signed-off-by: Mattia Eleuteri <[email protected]>
kubevirt/csi-driver cb4770e4e475 adds the flag so the driver can run against KubeVirt without the deprecated HotplugVolumes gate: with it off, a volume missing from VM.spec is not unplugged through the VMI-level removevolume on a VM-owned VMI, which virt-api rejects once that gate is gone. Bump to it and hand the flag to the upstream controller service. The flag defaults to true, which keeps the detach path as it was. The wrapper's own hot-unplug override goes. It was a backport of kubevirt/csi-driver#184, and the previous pin already contained that change: upstream detaches from the VMI when the VM is gone and waits for the VMI status to drop the volume before returning, so the wrapper's re-check never found it attached. Keeping it would have meant a second copy of the new ownership rule behind a branch nothing reaches. The bump also brings in the upstream node-service changes merged since the previous pin: NodeUnpublishVolume now uses CleanupMountPoint instead of Unmount (kubevirt/csi-driver#378), and device lookup runs udevadm trigger on block devices without a serial (kubevirt/csi-driver#363). The image carries no udevadm, so that rescan only logs an error and the lookup behaves as before. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
kubevirt/csi-driver cb4770e4e475 adds the flag so the driver can run against KubeVirt without the deprecated HotplugVolumes gate: with it off, a volume missing from VM.spec is not unplugged through the VMI-level removevolume on a VM-owned VMI, which virt-api rejects once that gate is gone. Bump to it and hand the flag to the upstream controller service. The flag defaults to true, which keeps the detach path as it was. The wrapper's own hot-unplug override goes. It was a backport of kubevirt/csi-driver#184, and the previous pin already contained that change: upstream detaches from the VMI when the VM is gone and waits for the VMI status to drop the volume before returning, so the wrapper's re-check never found it attached. Keeping it would have meant a second copy of the new ownership rule behind a branch nothing reaches. The bump also brings in the upstream node-service changes merged since the previous pin: NodeUnpublishVolume now uses CleanupMountPoint instead of Unmount (kubevirt/csi-driver#378), and device lookup runs udevadm trigger on block devices without a serial (kubevirt/csi-driver#363). The image carries no udevadm, so that rescan only logs an error and the lookup behaves as before. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
kubevirt/csi-driver cb4770e4e475 adds the flag so the driver can run against KubeVirt without the deprecated HotplugVolumes gate: with it off, a volume missing from VM.spec is not unplugged through the VMI-level removevolume on a VM-owned VMI, which virt-api rejects once that gate is gone. Bump to it and hand the flag to the upstream controller service. The flag defaults to true, which keeps the detach path as it was. cb4770e4e475 is the previous pin plus that one change. The wrapper's own hot-unplug override goes. It was a backport of kubevirt/csi-driver#184, and the previous pin already contained that change: upstream detaches from the VMI when the VM is gone and waits for the VMI status to drop the volume before returning, so the wrapper's re-check never found it attached. Keeping it would have meant a second copy of the new ownership rule behind a branch nothing reaches. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
kubevirt/csi-driver cb4770e4e475 adds the flag so the driver can run against KubeVirt without the deprecated HotplugVolumes gate: with it off, a volume missing from VM.spec is not unplugged through the VMI-level removevolume on a VM-owned VMI, which virt-api rejects once that gate is gone. Bump to it and hand the flag to the upstream controller service. The flag defaults to true, which keeps the detach path as it was. cb4770e4e475 is the previous pin plus that one change. The wrapper's own hot-unplug override goes. It was a backport of kubevirt/csi-driver#184, and the previous pin already contained that change: upstream detaches from the VMI when the VM is gone and waits for the VMI status to drop the volume before returning, so the wrapper's re-check never found it attached. Keeping it would have meant a second copy of the new ownership rule behind a branch nothing reaches. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
…gate (#4451) ## What this PR does This lets kubevirt-csi detach volumes without the deprecated KubeVirt `HotplugVolumes` gate. The default stays as it is, so nothing changes at runtime yet. Part of #4032. The driver got a switch upstream for exactly this, and I bumped to it. kubevirt/csi-driver#382 added `--enable-vmi-hotplug-fallback`, merged as cb4770e4e475. With it set to false, the driver no longer calls the VMI-level `removevolume` on a VMI owned by a VM. virt-api rejects that call once `HotplugVolumes` is off. Our wrapper now passes the flag to upstream and no longer has its own hot-unplug path. `unpublishHotplugVolume` was a backport of kubevirt/csi-driver#184, and the previous pin already contained that change. Upstream detaches from the VMI when the VM is gone, then waits for the VMI status to drop the volume before it returns. So the wrapper's re-check never found the volume attached. Adding the ownership check there would have been dead code. Closes #4450 The chart leaves the flag at its default of true and does not render it. The driver image in the chart is stamped only at release preparation, so between releases main runs the previous release's binary, which does not know the flag. Go's `flag` package exits on an unknown flag. Rendering `=true` would crash-loop the CSI controller for no effect. The right value depends on the KubeVirt CR, not on the tenant, so the comment next to `HotplugVolumes` in `kubevirt-cr.yaml` points at the controller Deployment. The follow-up PR drops the gate and renders the flag as false. Nothing changes on the node side. Main already pins f5de10d39556, and cb4770e4e475 is that commit plus kubevirt/csi-driver#382. A Go unit test runs an unpublish through the wrapper during a teardown, with the flag on, off with a VM-owned VMI, and off with an orphaned VMI. It checks that the flag reaches upstream and that only upstream makes the VMI-level call. Upstream's own tests cover the ownership rule. A second test holds that an NFS volume only releases the node from its CiliumNetworkPolicy and never reaches the upstream hot-unplug. A helm-unittest pins that the chart renders no such argument. I haven't built the image or run e2e. Volume attach and detach on a tenant cluster in e2e is the real check. ### Screenshots No UI change. ### Downstream repositories - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: ### Release note ```release-note feat(kubernetes): kubevirt-csi supports --enable-vmi-hotplug-fallback, so it can detach volumes without the KubeVirt HotplugVolumes feature gate. The flag stays true for now and behaviour is unchanged. ```
) removeVolumeFromVm treats NotFound from GetWorkloadManagingVirtualMachine as "no VM", but then checks vm != nil. A typed client-go Get returns an empty, non-nil object together with NotFound, so the check passes and vm.Spec.Template, which is nil, is dereferenced. ControllerUnpublishVolume panics on every retry for a VMI that outlives its VM, and the volume stays on the VMI: the case the VMI fallback path was added for (#182, #184). Reset vm to nil on NotFound. The test mock now returns the empty object a real client returns, which makes the existing VM-gone tests panic without the fix. Signed-off-by: Moep90 <[email protected]>
What this PR does / why we need it:
ControllerUnpublishVolumecurrently returns success as soon as the parentVirtualMachineobject is missing, on the assumption that a missing VM means the hot-plug is already gone. In practice the VMI (and its hot-plug pod) can outlive the parent VM during a CAPI/CAPK teardown race — the infra-side storage device then stays exclusively attached to the source host, and the next attempt to attach the same volume to a different VM fails withMulti-Attach erroror, on backends like DRBD that enforce exclusive attachment, withfailed to set source device readwrite.The same divergence appears when virt-api updates
VM.specoptimistically on aremovevolumecall but virt-handler has not yet unplugged the QEMU device.This PR:
EnsureVolumeRemovedVMIto thekubevirt.Clientinterface, mirroring the existingEnsureVolumeRemovedVMbut inspectingVMI.status.volumeStatusinstead ofVM.spec.template.spec.volumes.VM not found = successshort-circuit inControllerUnpublishVolumewith a newvolumeStillAttachedhelper that consults both surfaces. The original During detach don't return error if VM is not found #119 unblock is preserved (the call still succeeds when the volume is genuinely gone from both); the new behaviour is that we now actually detach from the VMI when it still holds the hot-plug.removeVolumeFromVmso that it falls through toRemoveVolumeFromVMIwhen the parent VM is gone but the VMI still reports the hot-plug, closing the orphan path end-to-end.controller_test.gothat covers the "VM gone but VMI still has the hot-plug" path.We hit the bug 12 times across two tenant clusters in a single day before opening the linked issue. Full failure-mode catalogue and reproducer are in the issue.
Which issue(s) this PR fixes:
Fixes #182
Refs #83 #119 #134
Special notes for your reviewer:
ControllerUnpublishVolumewill now actually runRemoveVolumeFromVMIagainst the VMI — i.e. it will do the right thing instead of silently doing nothing. There is no CSI/API-surface change.EnsureVolumeRemovedVMI) to thekubevirt.Clientinterface. I did not updatepkg/kubevirt/mocked_client.go: the file is already stale relative to the interface (missingEnsureVolumeRemovedVMandEnsureVolumeAvailableVMamong others) andmake mockgenproduces a much larger diff than the orphan-volume fix justifies. A separate "regen mocks" PR would be the right home for that. The hand-written test fakes (ControllerClientMockinpkg/service/controller_test.goandfakeKubeVirtClientinsanity/sanity_test.go) are updated.DetachReconcilerto catch orphans this synchronous path cannot reach (external-attacher giving up after force-detach timeout, VMIs in mid-migration, legacy state accumulated before this fix). They can be reviewed and merged in any order.Release note: