consult DiskFillByType in host disk fill alert evaluation

Inserts a per-type threshold lookup before the global AgentDefaults.Disk
fallback in CheckHost's disk evaluation. inferDiskHardwareType maps the
disk device path to "nvme" (only NVMe is inferable today); when the
DiskFillByType map carries a matching key, that hysteresis threshold is
used, otherwise evaluation falls through to the existing global default.
Storage-type unified_eval branch is unchanged.

Tests prove: (a) NVMe device at 91% does not alert when DiskFillByType
sets the nvme trigger to 92; (b) the same device at 93% alerts at the
nvme threshold; (c) /dev/sda1 falls back to the global threshold; (d)
the storage-type alert branch is not regressed.
This commit is contained in:
rcourtman
2026-05-13 06:32:07 +01:00
parent 005873bdd5
commit 85fbbd4ef6
2 changed files with 181 additions and 0 deletions
+12
View File
@@ -371,6 +371,18 @@ func (m *Manager) CheckHost(host models.Host) {
effectiveDiskThreshold = ensureHysteresisThreshold(diskOverride.Disk)
}
}
// Per-type override: consult DiskFillByType if hardware type is inferable
// from the device path and no disk-specific override applied above.
if effectiveDiskThreshold == nil && thresholds.Disk != nil && thresholds.Disk.Trigger > 0 {
if hwType := inferDiskHardwareType(disk.Device); hwType != "" {
m.mu.RLock()
if th, ok := m.config.DiskFillByType[hwType]; ok {
t := th
effectiveDiskThreshold = &t
}
m.mu.RUnlock()
}
}
// Fall back to host-level threshold
if effectiveDiskThreshold == nil {
effectiveDiskThreshold = thresholds.Disk
+169
View File
@@ -0,0 +1,169 @@
package alerts
import (
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
// configureDiskTypeHostManager builds an alert manager configured with a
// per-type NVMe override (92/87) and a global agent disk default (90/85),
// with immediate alerting (no time threshold, no suppression).
func configureDiskTypeHostManager(t *testing.T) *Manager {
t.Helper()
m := newTestManager(t)
cfg := AlertConfig{
Enabled: true,
ActivationState: ActivationActive,
AgentDefaults: ThresholdConfig{
Disk: &HysteresisThreshold{Trigger: 90, Clear: 85},
},
DiskFillByType: map[string]HysteresisThreshold{
"nvme": {Trigger: 92, Clear: 87},
"sata": {Trigger: 90, Clear: 85},
"hdd": {Trigger: 85, Clear: 80},
},
Overrides: map[string]ThresholdConfig{},
TimeThresholds: map[string]int{},
SuppressionWindow: 0,
MinimumDelta: 0,
}
m.UpdateConfig(cfg)
m.mu.Lock()
m.config.TimeThresholds = map[string]int{}
m.config.MetricTimeThresholds = nil
m.config.SuppressionWindow = 0
m.config.MinimumDelta = 0
m.mu.Unlock()
m.ClearActiveAlerts()
return m
}
func TestHostDiskFillUsesPerTypeThresholdForNVMe(t *testing.T) {
m := configureDiskTypeHostManager(t)
hostBase := models.Host{
ID: "host-nvme",
Hostname: "host-nvme",
Status: "online",
}
// 91% fill on an NVMe device must NOT alert: nvme trigger is 92.
hostBelow := hostBase
hostBelow.Disks = []models.Disk{{
Mountpoint: "/",
Device: "/dev/nvme0n1p1",
Usage: 91.0,
Total: 1000,
Used: 910,
Free: 90,
}}
m.CheckHost(hostBelow)
diskResourceID, _ := hostDiskResourceID(hostBelow, hostBelow.Disks[0])
trackingKey := canonicalMetricStateID(diskResourceID, "disk")
if _, exists := testLookupActiveAlert(t, m, trackingKey); exists {
t.Fatalf("expected no alert for nvme disk at 91%% (nvme trigger 92), got active: %v", alertKeys(m))
}
m.mu.RLock()
if _, pending := m.pendingAlerts[trackingKey]; pending {
m.mu.RUnlock()
t.Fatalf("expected no pending alert for nvme disk at 91%%, but pendingAlerts has %q", trackingKey)
}
m.mu.RUnlock()
// 93% fill on the same NVMe device must alert at the per-type threshold.
hostAbove := hostBase
hostAbove.Disks = []models.Disk{{
Mountpoint: "/",
Device: "/dev/nvme0n1p1",
Usage: 93.0,
Total: 1000,
Used: 930,
Free: 70,
}}
m.CheckHost(hostAbove)
if _, exists := testLookupActiveAlert(t, m, trackingKey); !exists {
t.Fatalf("expected alert for nvme disk at 93%% (nvme trigger 92), active: %v", alertKeys(m))
}
}
func TestHostDiskFillFallsBackToGlobalForUnknownDevice(t *testing.T) {
m := configureDiskTypeHostManager(t)
host := models.Host{
ID: "host-sata",
Hostname: "host-sata",
Status: "online",
Disks: []models.Disk{{
Mountpoint: "/",
Device: "/dev/sda1",
Usage: 91.0,
Total: 1000,
Used: 910,
Free: 90,
}},
}
m.CheckHost(host)
diskResourceID, _ := hostDiskResourceID(host, host.Disks[0])
trackingKey := canonicalMetricStateID(diskResourceID, "disk")
if _, exists := testLookupActiveAlert(t, m, trackingKey); !exists {
t.Fatalf("expected alert for /dev/sda1 at 91%% (global trigger 90, sata key dormant), active: %v", alertKeys(m))
}
}
func TestHostDiskFillPerTypeThresholdDoesNotOverrideDisabledGlobalDefault(t *testing.T) {
m := configureDiskTypeHostManager(t)
m.mu.Lock()
m.config.AgentDefaults.Disk = &HysteresisThreshold{Trigger: 0, Clear: 0}
m.mu.Unlock()
host := models.Host{
ID: "host-disabled-nvme",
Hostname: "host-disabled-nvme",
Status: "online",
Disks: []models.Disk{{
Mountpoint: "/",
Device: "/dev/nvme0n1p1",
Usage: 93.0,
Total: 1000,
Used: 930,
Free: 70,
}},
}
m.CheckHost(host)
diskResourceID, _ := hostDiskResourceID(host, host.Disks[0])
trackingKey := canonicalMetricStateID(diskResourceID, "disk")
if _, exists := testLookupActiveAlert(t, m, trackingKey); exists {
t.Fatalf("expected no alert when global agent disk threshold is disabled, active: %v", alertKeys(m))
}
m.mu.RLock()
if _, pending := m.pendingAlerts[trackingKey]; pending {
m.mu.RUnlock()
t.Fatalf("expected no pending alert when global agent disk threshold is disabled, but pendingAlerts has %q", trackingKey)
}
m.mu.RUnlock()
}
func TestStorageTypeBranchNotRegressed(t *testing.T) {
m := newTestManager(t)
configureUnifiedEvalManager(t, m, unifiedEvalBaseConfig())
m.CheckUnifiedResource(&UnifiedResourceInput{
ID: "storage-1",
Type: "storage",
Name: "storage-1",
Disk: &UnifiedResourceMetric{Percent: 92},
})
assertAlertPresent(t, m, canonicalMetricStateID("storage-1", "usage"))
}