mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-03 20:53:09 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
Reference in New Issue
Block a user