Apply Unraid empty-slot semantics in storage health

Use the reviewed placeholder-filesystem rule when storage health assesses structured Unraid state, while retaining explicit DISK_NP_MISSING members as critical evidence.

Change-source: pulse-maintainer

Contract-Neutral: Unraid fsType=auto placeholder normalization is applied consistently at storage-health assessment without changing contracts
This commit is contained in:
pulse-triage[bot]
2026-09-02 06:29:41 +01:00
parent 00ed84f472
commit 803d217e18
2 changed files with 28 additions and 2 deletions
+5 -1
View File
@@ -6,6 +6,7 @@ import (
"strings"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
unraidstatus "github.com/rcourtman/pulse-go-rewrite/internal/unraid"
)
func AssessHostRAIDArray(array models.HostRAIDArray) Assessment {
@@ -346,13 +347,16 @@ func unraidDiskStateCounts(storage models.HostUnraidStorage) (disabled, invalid,
func isUnraidEmptySlot(disk models.HostUnraidDisk) bool {
rawStatus := strings.ToUpper(strings.TrimSpace(disk.RawStatus))
status := strings.ToLower(strings.TrimSpace(disk.Status))
if unraidstatus.IsExplicitMissingMember(rawStatus) {
return false
}
if !strings.Contains(rawStatus, "DISK_NP") && status != "missing" {
return false
}
return strings.TrimSpace(disk.Device) == "" &&
strings.TrimSpace(disk.Model) == "" &&
strings.TrimSpace(disk.Serial) == "" &&
strings.TrimSpace(disk.Filesystem) == "" &&
!unraidstatus.HasMeaningfulFilesystem(disk.Filesystem) &&
disk.SizeBytes == 0
}
+23 -1
View File
@@ -237,7 +237,8 @@ func TestAssessUnraidStorageTreatsEmptyNoPresentSlotsAsUnprotected(t *testing.T)
{Name: "parity", Role: "parity", Status: "missing", RawStatus: "DISK_NP_DSBL"},
{Name: "md1p1", Device: "/dev/sde", Status: "online", RawStatus: "DISK_OK", SizeBytes: 5860522532},
{Name: "disk5", Role: "data", Status: "missing", RawStatus: "DISK_NP", Slot: 5},
{Name: "parity2", Role: "parity", Status: "missing", RawStatus: "DISK_NP_DSBL", Slot: 29},
{Name: "disk6", Role: "data", Status: "missing", RawStatus: "DISK_NP", Filesystem: "auto", Slot: 6},
{Name: "parity2", Role: "parity", Status: "missing", RawStatus: "DISK_NP_DSBL", Filesystem: "auto", Slot: 29},
},
})
@@ -281,6 +282,27 @@ func TestAssessUnraidStorageUsesDiskStatusesOverAggregateCounters(t *testing.T)
}
}
func TestAssessUnraidStoragePreservesExplicitMissingMemberWithoutIdentity(t *testing.T) {
assessment := AssessUnraidStorage(models.HostUnraidStorage{
ArrayStarted: true,
Disks: []models.HostUnraidDisk{
{Name: "parity", Role: "parity", Status: "online"},
{Name: "disk1", Role: "data", Status: "online"},
{Name: "disk2", Role: "data", Status: "missing", RawStatus: "DISK_NP_MISSING", Filesystem: "auto"},
},
})
if assessment.Level != RiskCritical {
t.Fatalf("Level = %q, want %q", assessment.Level, RiskCritical)
}
for _, reason := range assessment.Reasons {
if reason.Code == "unraid_missing_disks" {
return
}
}
t.Fatalf("explicit missing member without identity was not preserved: %+v", assessment.Reasons)
}
func TestAssessUnraidStoragePreservesGenuineStructuredMissingDisk(t *testing.T) {
assessment := AssessUnraidStorage(models.HostUnraidStorage{
ArrayStarted: true,