Pulse/internal/mdadm/mdadm_test.go
2025-11-30 16:42:12 +00:00

542 lines
16 KiB
Go

package mdadm
import (
"testing"
"github.com/rcourtman/pulse-go-rewrite/pkg/agents/host"
)
func TestParseDetail(t *testing.T) {
tests := []struct {
name string
device string
output string
want host.RAIDArray
wantErr bool
}{
{
name: "RAID1 healthy array",
device: "/dev/md0",
output: `/dev/md0:
Version : 1.2
Creation Time : Thu Jan 15 10:00:00 2025
Raid Level : raid1
Array Size : 102400000 (97.66 GiB 104.86 GB)
Used Dev Size : 102400000 (97.66 GiB 104.86 GB)
Raid Devices : 2
Total Devices : 2
Persistence : Superblock is persistent
Update Time : Thu Jan 16 12:00:00 2025
State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
Spare Devices : 0
Consistency Policy : resync
Name : server:0
UUID : 12345678:90abcdef:12345678:90abcdef
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1`,
want: host.RAIDArray{
Device: "/dev/md0",
Name: "server:0",
Level: "raid1",
State: "clean",
TotalDevices: 2,
ActiveDevices: 2,
WorkingDevices: 2,
FailedDevices: 0,
SpareDevices: 0,
UUID: "12345678:90abcdef:12345678:90abcdef",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "active sync", Slot: 1},
},
},
},
{
name: "RAID5 degraded array",
device: "/dev/md1",
output: `/dev/md1:
Version : 1.2
Creation Time : Wed Jan 14 08:00:00 2025
Raid Level : raid5
Array Size : 204800000 (195.31 GiB 209.72 GB)
Used Dev Size : 102400000 (97.66 GiB 104.86 GB)
Raid Devices : 3
Total Devices : 2
Persistence : Superblock is persistent
Update Time : Thu Jan 16 12:30:00 2025
State : clean, degraded
Active Devices : 2
Working Devices : 2
Failed Devices : 1
Spare Devices : 0
Name : server:1
UUID : abcdef12:34567890:abcdef12:34567890
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
- 0 0 1 removed
2 8 33 2 active sync /dev/sdc1
1 8 17 - faulty /dev/sdb1`,
want: host.RAIDArray{
Device: "/dev/md1",
Name: "server:1",
Level: "raid5",
State: "clean, degraded",
TotalDevices: 2,
ActiveDevices: 2,
WorkingDevices: 2,
FailedDevices: 1,
SpareDevices: 0,
UUID: "abcdef12:34567890:abcdef12:34567890",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdc1", State: "active sync", Slot: 2},
{Device: "/dev/sdb1", State: "faulty", Slot: -1},
},
},
},
{
name: "RAID6 rebuilding",
device: "/dev/md2",
output: `/dev/md2:
Version : 1.2
Creation Time : Wed Jan 14 08:00:00 2025
Raid Level : raid6
Array Size : 409600000 (390.62 GiB 419.43 GB)
Used Dev Size : 102400000 (97.66 GiB 104.86 GB)
Raid Devices : 6
Total Devices : 6
Persistence : Superblock is persistent
Update Time : Thu Jan 16 13:00:00 2025
State : active, recovering
Active Devices : 5
Working Devices : 6
Failed Devices : 0
Spare Devices : 1
Rebuild Status : 42% complete
Name : server:2
UUID : fedcba09:87654321:fedcba09:87654321
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1
2 8 33 2 active sync /dev/sdc1
3 8 49 3 active sync /dev/sdd1
6 8 81 4 spare rebuilding /dev/sdf1
5 8 65 5 active sync /dev/sde1`,
want: host.RAIDArray{
Device: "/dev/md2",
Name: "server:2",
Level: "raid6",
State: "active, recovering",
TotalDevices: 6,
ActiveDevices: 5,
WorkingDevices: 6,
FailedDevices: 0,
SpareDevices: 1,
UUID: "fedcba09:87654321:fedcba09:87654321",
RebuildPercent: 42.0,
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "active sync", Slot: 1},
{Device: "/dev/sdc1", State: "active sync", Slot: 2},
{Device: "/dev/sdd1", State: "active sync", Slot: 3},
{Device: "/dev/sdf1", State: "spare rebuilding", Slot: 6},
{Device: "/dev/sde1", State: "active sync", Slot: 5},
},
},
},
{
name: "RAID1 with spare devices",
device: "/dev/md3",
output: `/dev/md3:
Version : 1.2
Raid Level : raid1
Raid Devices : 2
Total Devices : 3
State : clean
Active Devices : 2
Working Devices : 3
Failed Devices : 0
Spare Devices : 1
Name : server:3
UUID : 11223344:55667788:99aabbcc:ddeeff00
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1
2 8 33 - spare /dev/sdc1`,
want: host.RAIDArray{
Device: "/dev/md3",
Name: "server:3",
Level: "raid1",
State: "clean",
TotalDevices: 3,
ActiveDevices: 2,
WorkingDevices: 3,
FailedDevices: 0,
SpareDevices: 1,
UUID: "11223344:55667788:99aabbcc:ddeeff00",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "active sync", Slot: 1},
{Device: "/dev/sdc1", State: "spare", Slot: -1},
},
},
},
{
name: "RAID10 array",
device: "/dev/md4",
output: `/dev/md4:
Version : 1.2
Raid Level : raid10
Array Size : 209715200 (200.00 GiB 214.75 GB)
Raid Devices : 4
Total Devices : 4
State : clean
Active Devices : 4
Working Devices : 4
Failed Devices : 0
Spare Devices : 0
Layout : near=2
Chunk Size : 512K
Name : server:4
UUID : aabbccdd:eeff0011:22334455:66778899
Number Major Minor RaidDevice State
0 8 1 0 active sync set-A /dev/sda1
1 8 17 1 active sync set-B /dev/sdb1
2 8 33 2 active sync set-A /dev/sdc1
3 8 49 3 active sync set-B /dev/sdd1`,
want: host.RAIDArray{
Device: "/dev/md4",
Name: "server:4",
Level: "raid10",
State: "clean",
TotalDevices: 4,
ActiveDevices: 4,
WorkingDevices: 4,
FailedDevices: 0,
SpareDevices: 0,
UUID: "aabbccdd:eeff0011:22334455:66778899",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync set-A", Slot: 0},
{Device: "/dev/sdb1", State: "active sync set-B", Slot: 1},
{Device: "/dev/sdc1", State: "active sync set-A", Slot: 2},
{Device: "/dev/sdd1", State: "active sync set-B", Slot: 3},
},
},
},
{
name: "RAID0 array (striped)",
device: "/dev/md5",
output: `/dev/md5:
Version : 1.2
Raid Level : raid0
Array Size : 209715200 (200.00 GiB 214.75 GB)
Raid Devices : 2
Total Devices : 2
State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
Spare Devices : 0
Chunk Size : 512K
Name : server:5
UUID : 12ab34cd:56ef78gh:90ij12kl:34mn56op
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1`,
want: host.RAIDArray{
Device: "/dev/md5",
Name: "server:5",
Level: "raid0",
State: "clean",
TotalDevices: 2,
ActiveDevices: 2,
WorkingDevices: 2,
FailedDevices: 0,
SpareDevices: 0,
UUID: "12ab34cd:56ef78gh:90ij12kl:34mn56op",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "active sync", Slot: 1},
},
},
},
{
name: "reshaping array",
device: "/dev/md6",
output: `/dev/md6:
Version : 1.2
Raid Level : raid5
Raid Devices : 4
Total Devices : 4
State : active, reshaping
Active Devices : 4
Working Devices : 4
Failed Devices : 0
Spare Devices : 0
Reshape Status : 23.5% complete
UUID : 99887766:55443322:11009988:77665544
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1
2 8 33 2 active sync /dev/sdc1
3 8 49 3 active sync /dev/sdd1`,
want: host.RAIDArray{
Device: "/dev/md6",
Level: "raid5",
State: "active, reshaping",
TotalDevices: 4,
ActiveDevices: 4,
WorkingDevices: 4,
FailedDevices: 0,
SpareDevices: 0,
UUID: "99887766:55443322:11009988:77665544",
RebuildPercent: 23.5,
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "active sync", Slot: 1},
{Device: "/dev/sdc1", State: "active sync", Slot: 2},
{Device: "/dev/sdd1", State: "active sync", Slot: 3},
},
},
},
{
name: "array with multiple faulty and spare devices",
device: "/dev/md7",
output: `/dev/md7:
Version : 1.2
Raid Level : raid5
Raid Devices : 3
Total Devices : 5
State : clean, degraded
Active Devices : 2
Working Devices : 3
Failed Devices : 2
Spare Devices : 1
UUID : ffeeddcc:bbaa9988:77665544:33221100
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
- 0 0 1 removed
2 8 33 2 active sync /dev/sdc1
3 8 49 - spare /dev/sdd1
1 8 17 - faulty /dev/sdb1
4 8 65 - faulty /dev/sde1`,
want: host.RAIDArray{
Device: "/dev/md7",
Level: "raid5",
State: "clean, degraded",
TotalDevices: 5,
ActiveDevices: 2,
WorkingDevices: 3,
FailedDevices: 2,
SpareDevices: 1,
UUID: "ffeeddcc:bbaa9988:77665544:33221100",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdc1", State: "active sync", Slot: 2},
{Device: "/dev/sdd1", State: "spare", Slot: -1},
{Device: "/dev/sdb1", State: "faulty", Slot: -1},
{Device: "/dev/sde1", State: "faulty", Slot: -1},
},
},
},
{
name: "empty output",
device: "/dev/md99",
output: "",
want: host.RAIDArray{
Device: "/dev/md99",
Devices: []host.RAIDDevice{},
},
},
{
name: "minimal output with only version",
device: "/dev/md10",
output: `/dev/md10:
Version : 1.2`,
want: host.RAIDArray{
Device: "/dev/md10",
Devices: []host.RAIDDevice{},
},
},
{
name: "output with extra whitespace",
device: "/dev/md11",
output: `
/dev/md11:
Version : 1.2
Raid Level : raid1
State : clean
Active Devices : 2
Working Devices : 2
Failed Devices : 0
Spare Devices : 0
UUID : 12341234:56785678:90ab90ab:cdefcdef
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 active sync /dev/sdb1
`,
want: host.RAIDArray{
Device: "/dev/md11",
Level: "raid1",
State: "clean",
ActiveDevices: 2,
WorkingDevices: 2,
FailedDevices: 0,
SpareDevices: 0,
UUID: "12341234:56785678:90ab90ab:cdefcdef",
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "active sync", Slot: 1},
},
},
},
{
name: "rebuild with decimal percentage",
device: "/dev/md12",
output: `/dev/md12:
Version : 1.2
Raid Level : raid1
Raid Devices : 2
Total Devices : 2
State : active, degraded, recovering
Active Devices : 1
Working Devices : 2
Failed Devices : 0
Spare Devices : 1
Rebuild Status : 67.8% complete
UUID : abcdef12:34567890:abcdef12:34567890
Number Major Minor RaidDevice State
0 8 1 0 active sync /dev/sda1
1 8 17 1 spare rebuilding /dev/sdb1`,
want: host.RAIDArray{
Device: "/dev/md12",
Level: "raid1",
State: "active, degraded, recovering",
TotalDevices: 2,
ActiveDevices: 1,
WorkingDevices: 2,
FailedDevices: 0,
SpareDevices: 1,
UUID: "abcdef12:34567890:abcdef12:34567890",
RebuildPercent: 67.8,
Devices: []host.RAIDDevice{
{Device: "/dev/sda1", State: "active sync", Slot: 0},
{Device: "/dev/sdb1", State: "spare rebuilding", Slot: 1},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseDetail(tt.device, tt.output)
if (err != nil) != tt.wantErr {
t.Errorf("parseDetail() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Compare fields
if got.Device != tt.want.Device {
t.Errorf("Device = %v, want %v", got.Device, tt.want.Device)
}
if got.Name != tt.want.Name {
t.Errorf("Name = %v, want %v", got.Name, tt.want.Name)
}
if got.Level != tt.want.Level {
t.Errorf("Level = %v, want %v", got.Level, tt.want.Level)
}
if got.State != tt.want.State {
t.Errorf("State = %v, want %v", got.State, tt.want.State)
}
if got.TotalDevices != tt.want.TotalDevices {
t.Errorf("TotalDevices = %v, want %v", got.TotalDevices, tt.want.TotalDevices)
}
if got.ActiveDevices != tt.want.ActiveDevices {
t.Errorf("ActiveDevices = %v, want %v", got.ActiveDevices, tt.want.ActiveDevices)
}
if got.WorkingDevices != tt.want.WorkingDevices {
t.Errorf("WorkingDevices = %v, want %v", got.WorkingDevices, tt.want.WorkingDevices)
}
if got.FailedDevices != tt.want.FailedDevices {
t.Errorf("FailedDevices = %v, want %v", got.FailedDevices, tt.want.FailedDevices)
}
if got.SpareDevices != tt.want.SpareDevices {
t.Errorf("SpareDevices = %v, want %v", got.SpareDevices, tt.want.SpareDevices)
}
if got.UUID != tt.want.UUID {
t.Errorf("UUID = %v, want %v", got.UUID, tt.want.UUID)
}
if got.RebuildPercent != tt.want.RebuildPercent {
t.Errorf("RebuildPercent = %v, want %v", got.RebuildPercent, tt.want.RebuildPercent)
}
// Compare devices
if len(got.Devices) != len(tt.want.Devices) {
t.Errorf("Devices count = %v, want %v", len(got.Devices), len(tt.want.Devices))
}
for i := range got.Devices {
if i >= len(tt.want.Devices) {
break
}
if got.Devices[i].Device != tt.want.Devices[i].Device {
t.Errorf("Device[%d].Device = %v, want %v", i, got.Devices[i].Device, tt.want.Devices[i].Device)
}
if got.Devices[i].State != tt.want.Devices[i].State {
t.Errorf("Device[%d].State = %v, want %v", i, got.Devices[i].State, tt.want.Devices[i].State)
}
if got.Devices[i].Slot != tt.want.Devices[i].Slot {
t.Errorf("Device[%d].Slot = %v, want %v", i, got.Devices[i].Slot, tt.want.Devices[i].Slot)
}
}
})
}
}