mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 22:12:23 +00:00
@@ -133,7 +133,12 @@ it exposes a direct CPU percentage and otherwise fall back to Podman's
|
||||
wall-clock delta semantics rather than Docker's multi-core normalization, and
|
||||
Proxmox Ceph status decoding must accept manager standby entries as either bare
|
||||
names or structured objects so collector payload variations do not break the
|
||||
canonical monitoring path.
|
||||
canonical monitoring path. That same compatibility boundary also owns legacy
|
||||
Unraid raw-status normalization at host-agent ingest: when older agents send
|
||||
`rawStatus` without the newer normalized `status`, `internal/monitoring/monitor_agents.go`
|
||||
must derive the canonical disk status before storage-risk assessment runs so
|
||||
v5 aggregate counters do not override clearly healthy per-disk state during v6
|
||||
compatibility operation.
|
||||
VMware vSphere now also has a locked phase-1 ingestion boundary under this
|
||||
lane. The admitted direction is vCenter-only in phase 1, and monitoring must
|
||||
stay API-first through the
|
||||
|
||||
@@ -1740,12 +1740,18 @@ func (m *Monitor) ApplyHostReport(report agentshost.Report, tokenRecord *config.
|
||||
if report.Unraid != nil {
|
||||
disks := make([]models.HostUnraidDisk, 0, len(report.Unraid.Disks))
|
||||
for _, disk := range report.Unraid.Disks {
|
||||
device := strings.TrimSpace(disk.Device)
|
||||
rawStatus := strings.TrimSpace(disk.RawStatus)
|
||||
status := strings.TrimSpace(disk.Status)
|
||||
if status == "" {
|
||||
status = normalizeLegacyUnraidDiskStatus(rawStatus, device)
|
||||
}
|
||||
disks = append(disks, models.HostUnraidDisk{
|
||||
Name: strings.TrimSpace(disk.Name),
|
||||
Device: strings.TrimSpace(disk.Device),
|
||||
Device: device,
|
||||
Role: strings.TrimSpace(disk.Role),
|
||||
Status: strings.TrimSpace(disk.Status),
|
||||
RawStatus: strings.TrimSpace(disk.RawStatus),
|
||||
Status: status,
|
||||
RawStatus: rawStatus,
|
||||
Serial: strings.TrimSpace(disk.Serial),
|
||||
Filesystem: strings.TrimSpace(disk.Filesystem),
|
||||
SizeBytes: disk.SizeBytes,
|
||||
@@ -2063,6 +2069,31 @@ func normalizeHostDiskDevice(device string) string {
|
||||
return strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(device), "/dev/"))
|
||||
}
|
||||
|
||||
func normalizeLegacyUnraidDiskStatus(rawStatus, device string) string {
|
||||
status := strings.ToUpper(strings.TrimSpace(rawStatus))
|
||||
switch {
|
||||
case status == "":
|
||||
if strings.TrimSpace(device) != "" {
|
||||
return "online"
|
||||
}
|
||||
return ""
|
||||
case strings.Contains(status, "DISK_OK") || status == "OK":
|
||||
return "online"
|
||||
case strings.Contains(status, "DISK_DSBL") || strings.Contains(status, "DISABLED"):
|
||||
return "disabled"
|
||||
case strings.Contains(status, "DISK_NP") || strings.Contains(status, "MISSING") || strings.Contains(status, "NOT_INSTALLED"):
|
||||
return "missing"
|
||||
case strings.Contains(status, "DISK_INVALID") || strings.Contains(status, "INVALID"):
|
||||
return "invalid"
|
||||
case strings.Contains(status, "DISK_WRONG") || strings.Contains(status, "WRONG"):
|
||||
return "wrong"
|
||||
case strings.Contains(status, "DISK_ERROR") || strings.Contains(status, "ERROR"):
|
||||
return "error"
|
||||
default:
|
||||
return strings.ToLower(status)
|
||||
}
|
||||
}
|
||||
|
||||
func hostDiskIOMetricResourceID(host models.Host, io models.DiskIO) string {
|
||||
device := normalizeHostDiskDevice(io.Device)
|
||||
if device == "" {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/storagehealth"
|
||||
agentshost "github.com/rcourtman/pulse-go-rewrite/pkg/agents/host"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/metrics"
|
||||
)
|
||||
@@ -618,6 +619,72 @@ func TestApplyHostReportStoresUnraidTopology(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHostReportNormalizesLegacyUnraidRawStatuses(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
monitor := &Monitor{
|
||||
state: models.NewState(),
|
||||
alertManager: alerts.NewManager(),
|
||||
hostTokenBindings: make(map[string]string),
|
||||
config: &config.Config{},
|
||||
rateTracker: NewRateTracker(),
|
||||
}
|
||||
t.Cleanup(func() { monitor.alertManager.Stop() })
|
||||
|
||||
report := agentshost.Report{
|
||||
Agent: agentshost.AgentInfo{
|
||||
ID: "agent-tower-legacy",
|
||||
Version: "5.1.27",
|
||||
IntervalSeconds: 30,
|
||||
},
|
||||
Host: agentshost.HostInfo{
|
||||
ID: "machine-tower-legacy",
|
||||
Hostname: "tower",
|
||||
MachineID: "machine-tower-legacy",
|
||||
},
|
||||
Metrics: agentshost.Metrics{
|
||||
Memory: agentshost.MemoryMetric{TotalBytes: 1024, UsedBytes: 512, FreeBytes: 512, Usage: 50},
|
||||
},
|
||||
Unraid: &agentshost.UnraidStorage{
|
||||
ArrayStarted: true,
|
||||
ArrayState: "STARTED",
|
||||
SyncAction: "check",
|
||||
SyncProgress: 55,
|
||||
NumDisabled: 1,
|
||||
NumInvalid: 1,
|
||||
Disks: []agentshost.UnraidDisk{
|
||||
{Name: "parity", Device: "/dev/sdb", Role: "parity", RawStatus: "DISK_OK", Serial: "SERIAL-PARITY"},
|
||||
{Name: "disk1", Device: "/dev/sdc", Role: "data", RawStatus: "DISK_OK", Serial: "SERIAL-DATA"},
|
||||
},
|
||||
},
|
||||
Timestamp: time.Now().UTC(),
|
||||
}
|
||||
|
||||
host, err := monitor.ApplyHostReport(report, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyHostReport: %v", err)
|
||||
}
|
||||
|
||||
if host.Unraid == nil {
|
||||
t.Fatal("expected unraid topology on host")
|
||||
}
|
||||
for _, disk := range host.Unraid.Disks {
|
||||
if disk.Status != "online" {
|
||||
t.Fatalf("expected normalized online status from rawStatus, got %+v", host.Unraid.Disks)
|
||||
}
|
||||
}
|
||||
|
||||
assessment := storagehealth.AssessUnraidStorage(*host.Unraid)
|
||||
if assessment.Level != storagehealth.RiskWarning {
|
||||
t.Fatalf("assessment level = %q, want %q", assessment.Level, storagehealth.RiskWarning)
|
||||
}
|
||||
for _, reason := range assessment.Reasons {
|
||||
if reason.Code == "unraid_disabled_disks" || reason.Code == "unraid_invalid_disks" {
|
||||
t.Fatalf("unexpected aggregate-count reason after raw-status normalization: %+v", assessment.Reasons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHostReportFiltersVendorManagedSystemRAIDArrays(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user