mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
@@ -1658,7 +1658,11 @@ the intentionally sparse public response.
|
||||
baseline for Linux md arrays. `mdadm --detail` may enrich level, state,
|
||||
member, UUID, and rebuild fields when available, but missing or failing
|
||||
mdadm detail probes must not hide a kernel-reported md array from the
|
||||
unified agent report.
|
||||
unified agent report. Vendor fixed-width role maps must not be treated as
|
||||
missing members: a QNAP system RAID1 array that reports `[24/2]` while both
|
||||
active members occupy roles 24 and 25 has two configured members, not 22
|
||||
failed disks; ordinary in-range degraded maps such as `[4/3] [UUU_]` retain
|
||||
their missing-member semantics (#1688).
|
||||
Server-side lifecycle admission must preserve that same continuity across
|
||||
Pulse restart and upgrade. When a standalone host comes back with the same
|
||||
durable machine/report identity and token continuity, the shared admission
|
||||
|
||||
@@ -251,6 +251,30 @@ func TestBuildReport(t *testing.T) {
|
||||
mc.raidArraysFn = nil
|
||||
})
|
||||
|
||||
t.Run("RAID collection preserves QNAP sparse role map health", func(t *testing.T) {
|
||||
mc.raidArraysFn = func(ctx context.Context) ([]agentshost.RAIDArray, error) {
|
||||
return parseMDStatArrays(`md13 : active raid1 sdb4[25] sda4[24]
|
||||
458880 blocks super 1.0 [24/2] [UU______________________]
|
||||
bitmap: 1/1 pages [4KB], 65536KB chunk`), nil
|
||||
}
|
||||
|
||||
report, err := agent.buildReport(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("buildReport failed: %v", err)
|
||||
}
|
||||
if len(report.RAID) != 1 {
|
||||
t.Fatalf("expected 1 RAID array, got %d", len(report.RAID))
|
||||
}
|
||||
array := report.RAID[0]
|
||||
if array.Device != "/dev/md13" || array.State != "active" {
|
||||
t.Fatalf("unexpected QNAP RAID array summary: %+v", array)
|
||||
}
|
||||
if array.TotalDevices != 2 || array.ActiveDevices != 2 || array.WorkingDevices != 2 || array.FailedDevices != 0 {
|
||||
t.Fatalf("unexpected QNAP RAID array counts: %+v", array)
|
||||
}
|
||||
mc.raidArraysFn = nil
|
||||
})
|
||||
|
||||
// Test case 4: Ceph collection
|
||||
t.Run("Ceph collection", func(t *testing.T) {
|
||||
mc.cephStatusFn = func(ctx context.Context) (*CephClusterStatus, error) {
|
||||
|
||||
@@ -214,9 +214,6 @@ func parseMDStatArraySection(deviceName string, section []string) agentshost.RAI
|
||||
active, _ := strconv.Atoi(matches[2])
|
||||
array.TotalDevices = total
|
||||
array.ActiveDevices = active
|
||||
if total > active && !strings.Contains(array.State, "degraded") {
|
||||
array.State = appendMDStatState(array.State, "degraded")
|
||||
}
|
||||
}
|
||||
|
||||
if array.Operation == "" {
|
||||
@@ -255,6 +252,15 @@ func parseMDStatArraySection(deviceName string, section []string) agentshost.RAI
|
||||
if array.TotalDevices == 0 {
|
||||
array.TotalDevices = array.ActiveDevices
|
||||
}
|
||||
if mdStatUsesSparseRoleBitmap(array, failedDevices) {
|
||||
// QNAP's system RAID1 arrays expose a fixed-width bay/role bitmap such
|
||||
// as [24/2] [UU______________________], while their active members use
|
||||
// roles 24 and 25. Those unused bitmap positions are not failed disks.
|
||||
array.TotalDevices = array.ActiveDevices
|
||||
}
|
||||
if array.TotalDevices > array.ActiveDevices && !strings.Contains(array.State, "degraded") {
|
||||
array.State = appendMDStatState(array.State, "degraded")
|
||||
}
|
||||
if failedDevices == 0 && array.TotalDevices > array.ActiveDevices {
|
||||
failedDevices = array.TotalDevices - array.ActiveDevices
|
||||
}
|
||||
@@ -265,6 +271,30 @@ func parseMDStatArraySection(deviceName string, section []string) agentshost.RAI
|
||||
return array
|
||||
}
|
||||
|
||||
func mdStatUsesSparseRoleBitmap(array agentshost.RAIDArray, failedDevices int) bool {
|
||||
if !strings.EqualFold(array.State, "active") ||
|
||||
!strings.EqualFold(array.Level, "raid1") ||
|
||||
failedDevices != 0 ||
|
||||
array.TotalDevices <= array.ActiveDevices ||
|
||||
array.ActiveDevices == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
activeMembers := 0
|
||||
for _, device := range array.Devices {
|
||||
state := strings.ToLower(device.State)
|
||||
if strings.Contains(state, "faulty") || strings.Contains(state, "spare") {
|
||||
continue
|
||||
}
|
||||
activeMembers++
|
||||
if device.Slot < array.TotalDevices {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return activeMembers == array.ActiveDevices
|
||||
}
|
||||
|
||||
func parseMDStatDeviceToken(token string, arrayState string) (agentshost.RAIDDevice, bool) {
|
||||
token = strings.Trim(token, ",")
|
||||
matches := mdTokenRe.FindStringSubmatch(token)
|
||||
|
||||
@@ -699,6 +699,38 @@ unused devices: <none>`
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMDStatArraysQNAPSystemRAIDRoleBitmap(t *testing.T) {
|
||||
mdstat := `Personalities : [linear] [raid0] [raid1] [raid10] [raid6] [raid5] [raid4] [multipath]
|
||||
md1 : active raid1 sdb3[3] sda3[2]
|
||||
3897062912 blocks super 1.0 [2/2] [UU]
|
||||
|
||||
md321 : active raid1 sda5[2] sdb5[0]
|
||||
6702656 blocks super 1.0 [2/2] [UU]
|
||||
bitmap: 0/1 pages [0KB], 65536KB chunk
|
||||
|
||||
md13 : active raid1 sdb4[25] sda4[24]
|
||||
458880 blocks super 1.0 [24/2] [UU______________________]
|
||||
bitmap: 1/1 pages [4KB], 65536KB chunk
|
||||
|
||||
md9 : active raid1 sdb1[25] sda1[24]
|
||||
530048 blocks super 1.0 [24/2] [UU______________________]
|
||||
bitmap: 1/1 pages [4KB], 65536KB chunk`
|
||||
|
||||
arrays := parseMDStatArrays(mdstat)
|
||||
if len(arrays) != 4 {
|
||||
t.Fatalf("array count = %d, want 4", len(arrays))
|
||||
}
|
||||
for _, index := range []int{2, 3} {
|
||||
array := arrays[index]
|
||||
if array.State != "active" {
|
||||
t.Fatalf("%s state = %q, want active", array.Device, array.State)
|
||||
}
|
||||
if array.TotalDevices != 2 || array.ActiveDevices != 2 || array.WorkingDevices != 2 || array.FailedDevices != 0 {
|
||||
t.Fatalf("unexpected %s device counts: %+v", array.Device, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListArrayDevicesError(t *testing.T) {
|
||||
withReadProcMDStat(t, func() ([]byte, error) {
|
||||
return nil, errors.New("read failed")
|
||||
|
||||
Reference in New Issue
Block a user