fix(csi): repair a controller that is connected but carries no path - #411
Closed
schmidt-scaled wants to merge 1 commit into
Closed
schmidt-scaled wants to merge 1 commit into
schmidt-scaled wants to merge 1 commit into
Conversation
The path-recovery loop cannot fix the one failure mode that matters most. A controller can be connected while contributing NO path to the namespace head: its namespace never joined, so the device-scoped `nvme list-subsys` does not list it and numActive stays below expected -- but `nvme connect` refuses with "already connected", because at the controller level nothing is missing. The two views never reconcile and reconcileNonOptimizedPaths spins forever. K8sNativeResilientFailoverTest iteration 28 (2026-08-09): 106 such retries over 11 minutes for volume 638be965, not one of which produced a single packet to the target -- the target log shows no connect attempt after the original one. The volume ran at 2 of 3 paths the whole time and lost all I/O when the outage removed the other two: "no available path - failing I/O" and ext4 remounted read-only. Across that 42-hour run the same state affected many volumes (78726d0e 608 degraded reports, 64467bfa 437, 638be965 308), so the cluster was routinely below its configured redundancy with no operator-visible signal. connectMissingPath() tries the ordinary connect first and returns immediately unless it fails with exactly "already connected", so the normal path is unchanged. Only then does it locate the orphaned controller via a host-wide `nvme list-subsys` -- deliberately without a device argument, since the device-scoped form is precisely what omits it -- disconnect it, and reconnect, forcing a fresh controller and a fresh namespace scan. A 5-minute per-(NQN, IP:port) cooldown keeps a repair that does not stick from becoming a disconnect/reconnect loop at monitor cadence. The control-plane side of this incident (the source of the pathless subsystems) is fixed separately in sbcli 97ef965c2. NOT COMPILED: no Go toolchain was available in the environment this was written in. Every referenced symbol and signature was verified by inspection, but `go build ./...` and `go vet ./pkg/util/...` have not been run, and no test was added. Please verify before merging. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
|
Superseded by #429 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A controller can be connected while contributing no path to the namespace head. Its namespace never joined, so the device-scoped
nvme list-subsysdoes not list it andnumActivestays belowexpected— butnvme connectrefuses withalready connected, because at the controller level nothing is missing. The two views never reconcile andreconcileNonOptimizedPathsspins forever.This state is invisible to every layer:
nvme connectsucceeds, the target establishes qpairs, the client printsnew ctrl, and no keep-alive timeout or controller reset ever fires — the path simply does not exist for I/O routing.K8sNativeResilientFailoverTest iteration 28 (2026-08-09): 106 such retries over 11 minutes for volume
638be965, not one of which produced a single packet to the target. The volume ran at 2 of 3 paths the whole time and lost all I/O when the outage removed the other two —no available path - failing I/O, ext4 remounted read-only, FIOerr=5.Across that 42-hour run the same state affected many volumes (
78726d0e608 degraded reports,64467bfa437,638be965308), so the cluster was routinely below its configured redundancy with no operator-visible signal.Change
Two call sites (
reconcileNonOptimizedPaths,reconcileOptimizedPath) now callconnectMissingPath()instead ofconnectViaNVMe().connectMissingPath()tries the ordinary connect first and returns immediately unless it fails with exactlyalready connected— so the normal path is unchanged. Only then does it:nvme list-subsys -o json— deliberately without a device argument, since the device-scoped form is precisely what omits it;nvme disconnect -d <ctrl>(same command shape as the existingdisconnectDevicePath);A 5-minute per-
(NQN, IP:port)cooldown keeps a repair that does not stick from becoming a disconnect/reconnect loop at monitor cadence.Supporting helpers:
findControllerForAddress(),isAlreadyConnectedErr(),parseTrsvcid().Related
The control-plane side of this incident — the source of the pathless subsystems — is fixed separately in sbcli
97ef965c2.Reviewer notes
go build ./...andgo vet ./pkg/util/...have not been run, and no test was added. Please verify before merging.nvme list-subsysreports a controller whose namespace never joined the head, while the device-scoped form does not. This is consistent with the incident data (three controllers existed,numActivewas 2) but was not verified against a live host. If the assumption is wrong,findControllerForAddressreturns not-found and the caller emits a clearer error instead of silently spinning — degraded, not harmful.isAlreadyConnectedErrmatches on error text, which works becauseexecWithTimeoutfoldsCombinedOutputinto the returned error.