Repository navigation
fix(collector): preserve jmb39x-q device type and accept usable SMART JSON - #677
Merged
Merged
Conversation
… JSON QNAP TR-004 enclosures in Individual mode address each bay through the smartmontools jmb39x-q,N backend. Two independent collector defects made those disks unmonitorable. First, a comma-containing device type was torn apart at config parse time. Viper's default decode hook splits a scalar string on commas when the destination is a slice, so type: 'jmb39x-q,0' became the two bogus types 'jmb39x-q' and '0'. A comma is part of the smartctl device-type grammar, not a separator, so the hook is now replaced for the devices key and WeaklyTypedInput lifts the scalar into a one-element slice verbatim. The list form is unchanged. Configured types are also trimmed, and an explicitly empty type stays an empty but non-nil slice so the device keeps being dropped rather than falling back to the scanned type. Second, any non-zero smartctl exit aborted device identification. SmartCtlInfo masked the exit code with 0xBF, but the tolerant branch fell through into the pre-existing "return err", so no non-zero exit had ever survived. These enclosures report "Read Device Statistics page 0x00 failed" and exit 4 while printing a complete JSON document, so the disk registered with no model, serial or WWN. Since every InfluxDB query filters on the device_wwn tag, an empty WWN is what produced the "No Data" dashboard. Detection now applies the same rule the collection path and the server's upload validation already used: only bits 0x01 and 0x02 mean the payload cannot be trusted, because every other bit describes a condition of the disk rather than of the output. The three copies of that rule are replaced by a shared webapp/backend/pkg/smartctl package. Detection additionally rejects empty output, so genuinely unusable results are still refused. This supersedes the documented asymmetry between the detection and collection masks. That asymmetry described an intent the code never implemented; see ADR 0008. Note for existing users: an affected disk currently registers through the metadata-less identity fallback. Once its identity can be read it hashes to a new device id and the old entry remains behind with no data. Delete the stale entry.
4 tasks
Owner
Author
Merged (2026-07-28)Changes Made
Note: end-to-end validation on real TR-004 hardware is still outstanding, and the detect fixture is modeled rather than captured. Both are tracked in the issue comment. |
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.
Fixes #663.
Summary
QNAP TR-004 enclosures in Individual mode address each bay through the smartmontools
jmb39x-q,Nbackend. Two independent collector defects made those disks unmonitorable. Both are fixed here.Bug A: comma-containing device types were split at config parse time
The argument construction was never at fault. All three
--devicesites already append the type as a single element, and the shell layer usesexec.CommandContextwith no re-tokenization. The damage happened earlier: viper installs its ownstringToWeakSliceHookFunc(",")decode hook, which splits a scalar string when the destination is a slice.type: 'jmb39x-q,0'therefore decoded to the two bogus typesjmb39x-qand0.A comma belongs to the smartctl device-type grammar (
megaraid,14,sat,auto,jmb39x-q,0), it is not a separator between types.GetDeviceOverridesnow overridesDecodeHookfor thedeviceskey soWeaklyTypedInputlifts a scalar into a one-element slice verbatim. The list form is unchanged.Two edge cases that removing the hook opens up are handled by a new
normalizeDeviceOverrides: configured types are trimmed, and an explicitly empty type stays an empty but non-nil slice sobuildOverrideDeviceGroupkeeps dropping the device rather than falling back to the scanned type.Bug B: any non-zero smartctl exit aborted device identification
SmartCtlInfomasked the exit code with0xBF, but the tolerant branch logged "Continuing." and then fell straight into the pre-existingif err != nil { return err }. It was dead code: no non-zero smartctl exit had ever survived detection, including the bit-6 case the mask was added for.These enclosures report
Read Device Statistics page 0x00 failedand exit 4 on every run while printing a complete JSON document. The disk was therefore registered with no model, serial or WWN. The empty WWN is the actual cause of the reported "No Data": every InfluxDB query filters on thedevice_wwntag. This means users on the documented list-form workaround were still broken, because theirCollectcalls were publishing fine under the existing0x03rule while detection threw the identity away.Detection now applies the same rule the collection path and the server's
validateSmartExitStatusalready used: only bits0x01and0x02mean the payload cannot be trusted, since every other bit describes a condition of the disk rather than of the output. The three copies of that rule are replaced by a sharedwebapp/backend/pkg/smartctlpackage. Detection additionally rejects empty output, so genuinely unusable results are still refused.Behavior changes reviewers should weigh
Scalar comma semantics.
type: 'sat,auto'previously yielded the two typessatandauto. It now yields one. Both old outputs were nonsense as smartctl device types and neither has ever worked, so no realistic configuration can depend on the old behavior, and no compatibility path is included. The list form is unaffected;TestConfiguration_GetScanOverrides_Raidis the unchanged proof.Affected devices re-register under a new id.
deviceidis untouched andDeviceTypeis not an identity input, so the parsing change cannot re-id anything by itself. But a drive that is identity-less today resolves through thedevice_name+host_idfallback; once its model, serial and WWN can be read it hashes differently and appears as a new device, orphaning the old "No Data" row. This is unavoidable, and is the desired outcome. The troubleshooting doc tells affected users to delete the stale entry, whose history is empty-WWN and cannot be merged usefully.This supersedes a documented asymmetry. The knowledge base recorded the two exit-code masks as deliberately different and instructed against unifying them.
git log -S 0xBFshows the mask arrived as a narrow bit-6 spot-fix whose lines were inserted above the existingreturn err, so the documented tolerance never executed. The invariant described an intent the code never implemented. ADR 0008 records the decision.Tests
13 new tests, all passing.
jmb39x-qlayout, whitespace trimming, and the empty-vs-nil device type distinction.jmb39x-q,0asserted as one--deviceargument. The existingSmartCtlInfotest used an empty device type, so that branch had no coverage at all.--deviceargv asserted on both the--xalland FARM calls (the two sites besides detection that must stay identical), publish on non-fatal exit, and error report without publish on fatal exit.Collectpreviously had no coverage.Exit statuses come from a real
*exec.ExitError, produced by re-executing the test binary as a helper process rather than shelling out tosh -c, so the tests stay portable.Verified:
go test ./collector/..., backend handler exit-status tests,go vet,gofmt,go mod vendorclean, andgrep 0xBFreturns nothing.Not covered here
No TR-004 hardware was available, so the end-to-end acceptance criterion is unverified on real hardware. The detect fixture is modeled on a standard SAT
--info --jsonresponse rather than captured from a realjmb39x-qdevice; replacing it with sanitized real output would make the regression test stronger.