test: Add CheckDiskHealth normal path tests
Coverage for CheckDiskHealth: 51% -> 98% Tests cover: - Healthy disk (PASSED/OK) creates no alert - Failed non-Samsung disk creates critical alert - Alert cleared when disk health recovers - Low wearout (<10%) creates warning alert - Wearout alert updates on subsequent checks - Wearout alert cleared when wearout >= 10% - Empty/UNKNOWN health creates no alert
This commit is contained in:
parent
c04d559700
commit
219c568649
1 changed files with 286 additions and 0 deletions
|
|
@ -1715,6 +1715,292 @@ func TestCheckDiskHealthClearsExistingSamsung980Alerts(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthHealthyDiskNoAlert(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
// Non-Samsung disk with PASSED health should not create alert
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/sda",
|
||||||
|
Model: "Western Digital WD40EFZX",
|
||||||
|
Serial: "WD-WCC4E0123456",
|
||||||
|
Type: "hdd",
|
||||||
|
Health: "PASSED",
|
||||||
|
Wearout: 0, // N/A for HDD
|
||||||
|
Size: 4000787030016,
|
||||||
|
}
|
||||||
|
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
healthAlertID := "disk-health-test-instance-pve-node1-/dev/sda"
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; exists {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
t.Fatalf("expected no health alert for healthy disk with PASSED status")
|
||||||
|
}
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
// Also test with "OK" status
|
||||||
|
disk.Health = "OK"
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; exists {
|
||||||
|
t.Fatalf("expected no health alert for healthy disk with OK status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthFailedDiskCreatesAlert(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
// Non-Samsung disk with FAILED health should create alert
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/sdb",
|
||||||
|
Model: "Seagate ST2000DM008",
|
||||||
|
Serial: "ZA123456",
|
||||||
|
Type: "hdd",
|
||||||
|
Health: "FAILED",
|
||||||
|
Wearout: 0,
|
||||||
|
Size: 2000398934016,
|
||||||
|
}
|
||||||
|
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
healthAlertID := "disk-health-test-instance-pve-node1-/dev/sdb"
|
||||||
|
alert, exists := m.activeAlerts[healthAlertID]
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("expected health alert to be created for failed disk")
|
||||||
|
}
|
||||||
|
|
||||||
|
if alert.Level != AlertLevelCritical {
|
||||||
|
t.Errorf("expected critical alert level, got %s", alert.Level)
|
||||||
|
}
|
||||||
|
if alert.Type != "disk-health" {
|
||||||
|
t.Errorf("expected type disk-health, got %s", alert.Type)
|
||||||
|
}
|
||||||
|
if alert.Node != "pve-node1" {
|
||||||
|
t.Errorf("expected node pve-node1, got %s", alert.Node)
|
||||||
|
}
|
||||||
|
if alert.Instance != "test-instance" {
|
||||||
|
t.Errorf("expected instance test-instance, got %s", alert.Instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthRecoveryAlertCleared(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/sdc",
|
||||||
|
Model: "Intel SSDSC2BB480G4",
|
||||||
|
Serial: "BTWL123456789",
|
||||||
|
Type: "ssd",
|
||||||
|
Health: "FAILED",
|
||||||
|
Wearout: 50,
|
||||||
|
Size: 480103981056,
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check creates alert
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
healthAlertID := "disk-health-test-instance-pve-node1-/dev/sdc"
|
||||||
|
m.mu.RLock()
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; !exists {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
t.Fatalf("expected health alert to be created")
|
||||||
|
}
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
// Disk health recovers
|
||||||
|
disk.Health = "PASSED"
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; exists {
|
||||||
|
t.Fatalf("expected health alert to be cleared after recovery")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthLowWearoutCreatesAlert(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
// SSD with low wearout (less than 10% life remaining)
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/nvme1n1",
|
||||||
|
Model: "Crucial CT1000MX500",
|
||||||
|
Serial: "12345678ABCD",
|
||||||
|
Type: "nvme",
|
||||||
|
Health: "PASSED",
|
||||||
|
Wearout: 5, // Only 5% life remaining
|
||||||
|
Size: 1000204886016,
|
||||||
|
}
|
||||||
|
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
wearoutAlertID := "disk-wearout-test-instance-pve-node1-/dev/nvme1n1"
|
||||||
|
alert, exists := m.activeAlerts[wearoutAlertID]
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("expected wearout alert to be created for disk with low life remaining")
|
||||||
|
}
|
||||||
|
|
||||||
|
if alert.Level != AlertLevelWarning {
|
||||||
|
t.Errorf("expected warning alert level, got %s", alert.Level)
|
||||||
|
}
|
||||||
|
if alert.Type != "disk-wearout" {
|
||||||
|
t.Errorf("expected type disk-wearout, got %s", alert.Type)
|
||||||
|
}
|
||||||
|
if alert.Value != 5 {
|
||||||
|
t.Errorf("expected value 5, got %f", alert.Value)
|
||||||
|
}
|
||||||
|
if alert.Threshold != 10.0 {
|
||||||
|
t.Errorf("expected threshold 10.0, got %f", alert.Threshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthWearoutAlertUpdatesOnSubsequentChecks(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/nvme2n1",
|
||||||
|
Model: "Kingston SA2000M8",
|
||||||
|
Serial: "50026B768A123456",
|
||||||
|
Type: "nvme",
|
||||||
|
Health: "PASSED",
|
||||||
|
Wearout: 8,
|
||||||
|
Size: 500107862016,
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check creates alert
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
wearoutAlertID := "disk-wearout-test-instance-pve-node1-/dev/nvme2n1"
|
||||||
|
m.mu.RLock()
|
||||||
|
alert, exists := m.activeAlerts[wearoutAlertID]
|
||||||
|
if !exists {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
t.Fatalf("expected wearout alert to be created")
|
||||||
|
}
|
||||||
|
firstLastSeen := alert.LastSeen
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
// Wait a moment to ensure time difference
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Wearout decreases further
|
||||||
|
disk.Wearout = 6
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
|
alert, exists = m.activeAlerts[wearoutAlertID]
|
||||||
|
if !exists {
|
||||||
|
t.Fatalf("expected wearout alert to still exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alert.LastSeen.After(firstLastSeen) {
|
||||||
|
t.Errorf("expected LastSeen to be updated, got %v (original: %v)", alert.LastSeen, firstLastSeen)
|
||||||
|
}
|
||||||
|
if alert.Value != 6 {
|
||||||
|
t.Errorf("expected value to be updated to 6, got %f", alert.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthWearoutRecoveryAlertCleared(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/sdd",
|
||||||
|
Model: "ADATA SU800",
|
||||||
|
Serial: "2J012345678",
|
||||||
|
Type: "ssd",
|
||||||
|
Health: "PASSED",
|
||||||
|
Wearout: 5,
|
||||||
|
Size: 256060514304,
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check creates wearout alert
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
wearoutAlertID := "disk-wearout-test-instance-pve-node1-/dev/sdd"
|
||||||
|
m.mu.RLock()
|
||||||
|
if _, exists := m.activeAlerts[wearoutAlertID]; !exists {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
t.Fatalf("expected wearout alert to be created")
|
||||||
|
}
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
// Wearout recovers (replaced drive, or misread corrected)
|
||||||
|
disk.Wearout = 95
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if _, exists := m.activeAlerts[wearoutAlertID]; exists {
|
||||||
|
t.Fatalf("expected wearout alert to be cleared after recovery")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckDiskHealthEmptyOrUnknownHealthNoAlert(t *testing.T) {
|
||||||
|
m := newTestManager(t)
|
||||||
|
m.ClearActiveAlerts()
|
||||||
|
|
||||||
|
disk := proxmox.Disk{
|
||||||
|
DevPath: "/dev/sde",
|
||||||
|
Model: "Generic USB Storage",
|
||||||
|
Serial: "USB123456",
|
||||||
|
Type: "hdd",
|
||||||
|
Health: "", // Empty health - SMART not supported
|
||||||
|
Wearout: 0,
|
||||||
|
Size: 128043712512,
|
||||||
|
}
|
||||||
|
|
||||||
|
healthAlertID := "disk-health-test-instance-pve-node1-/dev/sde"
|
||||||
|
|
||||||
|
// Empty health should not create alert
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; exists {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
t.Fatalf("expected no health alert for disk with empty health status")
|
||||||
|
}
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
// UNKNOWN health should not create alert
|
||||||
|
disk.Health = "UNKNOWN"
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; exists {
|
||||||
|
m.mu.RUnlock()
|
||||||
|
t.Fatalf("expected no health alert for disk with UNKNOWN health status")
|
||||||
|
}
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
// Lowercase "unknown" should also not create alert (normalized to uppercase)
|
||||||
|
disk.Health = "unknown"
|
||||||
|
m.CheckDiskHealth("test-instance", "pve-node1", disk)
|
||||||
|
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if _, exists := m.activeAlerts[healthAlertID]; exists {
|
||||||
|
t.Fatalf("expected no health alert for disk with lowercase unknown health status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisableAllStorageClearsExistingAlerts(t *testing.T) {
|
func TestDisableAllStorageClearsExistingAlerts(t *testing.T) {
|
||||||
m := newTestManager(t)
|
m := newTestManager(t)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue