From 1ef7ad9de033301b345cbc481ae7f0201a692fd5 Mon Sep 17 00:00:00 2001 From: Singee Date: Thu, 13 Apr 2023 16:46:58 +0800 Subject: [PATCH 1/5] Support parse raid type for mdstat Note: rebased on top of master for reformatting Signed-off-by: Singee Signed-off-by: Robin H. Johnson --- mdstat.go | 10 ++++++++++ mdstat_test.go | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/mdstat.go b/mdstat.go index 67a9d2b4..e31fc1d5 100644 --- a/mdstat.go +++ b/mdstat.go @@ -34,6 +34,8 @@ var ( type MDStat struct { // Name of the device. Name string + // raid type of the device. + Type string // activity-state of the device. ActivityState string // Number of active disks. @@ -97,6 +99,13 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { mdName := deviceFields[0] // mdx state := deviceFields[2] // active or inactive + mdType := "" + if len(deviceFields) > 3 && strings.HasPrefix(deviceFields[3], "raid") { + mdType = deviceFields[3] // raid1, raid5, etc. + } else if len(deviceFields) > 4 && strings.HasPrefix(deviceFields[4], "raid") { + mdType = deviceFields[4] + } + if len(lines) <= i+3 { return nil, fmt.Errorf("%w: Too few lines for md device: %q", ErrFileParse, mdName) } @@ -150,6 +159,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { mdStats = append(mdStats, MDStat{ Name: mdName, + Type: mdType, ActivityState: state, DisksActive: active, DisksFailed: fail, diff --git a/mdstat_test.go b/mdstat_test.go index 7a577edc..81192103 100644 --- a/mdstat_test.go +++ b/mdstat_test.go @@ -30,6 +30,7 @@ func TestFS_MDStat(t *testing.T) { refs := map[string]MDStat{ "md127": { Name: "md127", + Type: "raid1", ActivityState: "active", DisksActive: 2, DisksTotal: 2, @@ -45,6 +46,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdi2", "sdj2"}}, "md0": { Name: "md0", + Type: "raid1", ActivityState: "active", DisksActive: 2, DisksTotal: 2, @@ -60,6 +62,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdi1", "sdj1"}}, "md4": { Name: "md4", + Type: "raid1", ActivityState: "inactive", DisksActive: 0, DisksTotal: 0, @@ -75,6 +78,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sda3", "sdb3"}}, "md6": { Name: "md6", + Type: "raid1", ActivityState: "recovering", DisksActive: 1, DisksTotal: 2, @@ -90,6 +94,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb2", "sdc", "sda2"}}, "md3": { Name: "md3", + Type: "raid6", ActivityState: "active", DisksActive: 8, DisksTotal: 8, @@ -105,6 +110,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}}, "md8": { Name: "md8", + Type: "raid1", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, @@ -120,6 +126,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb1", "sda1", "sdc", "sde"}}, "md7": { Name: "md7", + Type: "raid6", ActivityState: "active", DisksActive: 3, DisksTotal: 4, @@ -135,6 +142,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb1", "sde1", "sdd1", "sdc1"}}, "md9": { Name: "md9", + Type: "raid1", ActivityState: "resyncing", DisksActive: 4, DisksTotal: 4, @@ -150,6 +158,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}}, "md10": { Name: "md10", + Type: "raid0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, @@ -165,6 +174,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sda1", "sdb1"}}, "md11": { Name: "md11", + Type: "raid1", ActivityState: "resyncing", DisksActive: 2, DisksTotal: 2, @@ -180,6 +190,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}}, "md12": { Name: "md12", + Type: "raid0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, @@ -195,6 +206,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdc2", "sdd2"}}, "md120": { Name: "md120", + Type: "", ActivityState: "active", DisksActive: 2, DisksTotal: 2, @@ -210,6 +222,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sda1", "sdb1"}}, "md126": { Name: "md126", + Type: "raid0", ActivityState: "active", DisksActive: 2, DisksTotal: 2, @@ -225,6 +238,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb", "sdc"}}, "md219": { Name: "md219", + Type: "", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, @@ -240,6 +254,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdc", "sda"}}, "md00": { Name: "md00", + Type: "raid0", ActivityState: "active", DisksActive: 1, DisksTotal: 1, @@ -255,6 +270,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"xvdb"}}, "md101": { Name: "md101", + Type: "raid0", ActivityState: "active", DisksActive: 3, DisksTotal: 3, @@ -270,6 +286,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb", "sdd", "sdc"}}, "md201": { Name: "md201", + Type: "raid1", ActivityState: "checking", DisksActive: 2, DisksTotal: 2, From f2795b6f257575fbc6d95fbcc2affbe0fc483270 Mon Sep 17 00:00:00 2001 From: Singee Date: Thu, 13 Apr 2023 17:05:59 +0800 Subject: [PATCH 2/5] support linear type in mdstat Note: rebased on top of master for reformatting Signed-off-by: Singee Signed-off-by: Robin H. Johnson --- mdstat.go | 8 ++++++-- mdstat_test.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mdstat.go b/mdstat.go index e31fc1d5..8d7a25ae 100644 --- a/mdstat.go +++ b/mdstat.go @@ -100,9 +100,9 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { state := deviceFields[2] // active or inactive mdType := "" - if len(deviceFields) > 3 && strings.HasPrefix(deviceFields[3], "raid") { + if len(deviceFields) > 3 && isRaidType(deviceFields[3]) { mdType = deviceFields[3] // raid1, raid5, etc. - } else if len(deviceFields) > 4 && strings.HasPrefix(deviceFields[4], "raid") { + } else if len(deviceFields) > 4 && isRaidType(deviceFields[4]) { mdType = deviceFields[4] } @@ -179,6 +179,10 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { return mdStats, nil } +func isRaidType(mdType string) bool { + return strings.HasPrefix(mdType, "raid") || mdType == "linear" +} + func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) { statusFields := strings.Fields(statusLine) if len(statusFields) < 1 { diff --git a/mdstat_test.go b/mdstat_test.go index 81192103..6c477bc8 100644 --- a/mdstat_test.go +++ b/mdstat_test.go @@ -206,7 +206,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdc2", "sdd2"}}, "md120": { Name: "md120", - Type: "", + Type: "linear", ActivityState: "active", DisksActive: 2, DisksTotal: 2, From 8a07435bf87f0bea874c34de2b50821990cf1d22 Mon Sep 17 00:00:00 2001 From: Singee Date: Thu, 13 Apr 2023 17:12:47 +0800 Subject: [PATCH 3/5] change default type to unknown Note: rebased on top of master for reformatting Signed-off-by: Singee Signed-off-by: Robin H. Johnson --- mdstat.go | 2 +- mdstat_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mdstat.go b/mdstat.go index 8d7a25ae..e5a98eca 100644 --- a/mdstat.go +++ b/mdstat.go @@ -99,7 +99,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { mdName := deviceFields[0] // mdx state := deviceFields[2] // active or inactive - mdType := "" + mdType := "unknown" if len(deviceFields) > 3 && isRaidType(deviceFields[3]) { mdType = deviceFields[3] // raid1, raid5, etc. } else if len(deviceFields) > 4 && isRaidType(deviceFields[4]) { diff --git a/mdstat_test.go b/mdstat_test.go index 6c477bc8..c9d692b5 100644 --- a/mdstat_test.go +++ b/mdstat_test.go @@ -238,7 +238,7 @@ func TestFS_MDStat(t *testing.T) { Devices: []string{"sdb", "sdc"}}, "md219": { Name: "md219", - Type: "", + Type: "unknown", ActivityState: "inactive", DisksTotal: 0, DisksFailed: 0, From cd2a69e090d0a3a4c86b298f917086d2c4551bde Mon Sep 17 00:00:00 2001 From: Singee Date: Thu, 13 Apr 2023 17:44:44 +0800 Subject: [PATCH 4/5] optimize raid type check Signed-off-by: Singee --- mdstat.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mdstat.go b/mdstat.go index e5a98eca..55ca7452 100644 --- a/mdstat.go +++ b/mdstat.go @@ -99,11 +99,14 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { mdName := deviceFields[0] // mdx state := deviceFields[2] // active or inactive - mdType := "unknown" - if len(deviceFields) > 3 && isRaidType(deviceFields[3]) { - mdType = deviceFields[3] // raid1, raid5, etc. - } else if len(deviceFields) > 4 && isRaidType(deviceFields[4]) { - mdType = deviceFields[4] + mdType := "unknown" // raid1, raid5, etc. + if len(deviceFields) > 3 { // mdType may be in the 3rd or 4th field + if isRaidType(deviceFields[3]) { + mdType = deviceFields[3] + } else if len(deviceFields) > 4 && isRaidType(deviceFields[4]) { + // if the 3rd field is (...), the 4th field is the mdType + mdType = deviceFields[4] + } } if len(lines) <= i+3 { @@ -179,8 +182,11 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { return mdStats, nil } +// check if a string's format is like the mdType +// Rule 1: mdType should not be like (...) +// Rule 2: mdType should not be like sda[0] func isRaidType(mdType string) bool { - return strings.HasPrefix(mdType, "raid") || mdType == "linear" + return !strings.ContainsAny(mdType, "([") } func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) { From dca5b72491ac3a34e01dae0d7756d33ff2b41972 Mon Sep 17 00:00:00 2001 From: "Robin H. Johnson" Date: Thu, 3 Oct 2024 11:55:30 -0700 Subject: [PATCH 5/5] feat: expose MD raid component devices Expose what component devices are part of a MD raid device, as well as the most common flags per-component. This will enable a future node_exporter metric showing which component of a RAID had failed. Signed-off-by: Robin H. Johnson Signed-off-by: Robin H. Johnson --- mdstat.go | 96 ++++++++++++++++++++++++++++++++++++++------------ mdstat_test.go | 57 ++++++++++++++++++++---------- 2 files changed, 112 insertions(+), 41 deletions(-) diff --git a/mdstat.go b/mdstat.go index 55ca7452..e39f639d 100644 --- a/mdstat.go +++ b/mdstat.go @@ -27,9 +27,28 @@ var ( recoveryLinePctRE = regexp.MustCompile(`= (.+)%`) recoveryLineFinishRE = regexp.MustCompile(`finish=(.+)min`) recoveryLineSpeedRE = regexp.MustCompile(`speed=(.+)[A-Z]`) - componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`) + componentDeviceRE = regexp.MustCompile(`(.*)\[(\d+)\](\([SF]+\))?`) + personalitiesPrefix = "Personalities : " ) +type MDStatComponent struct { + // Name of the component device. + Name string + // DescriptorIndex number of component device, e.g. the order in the superblock. + DescriptorIndex int32 + // Flags per Linux drivers/md/md.[ch] as of v6.12-rc1 + // Subset that are exposed in mdstat + WriteMostly bool + Journal bool + Faulty bool // "Faulty" is what kernel source uses for "(F)" + Spare bool + Replacement bool + // Some additional flags that are NOT exposed in procfs today; they may + // be available via sysfs. + // In_sync, Bitmap_sync, Blocked, WriteErrorSeen, FaultRecorded, + // BlockedBadBlocks, WantReplacement, Candidate, ... +} + // MDStat holds info parsed from /proc/mdstat. type MDStat struct { // Name of the device. @@ -60,8 +79,8 @@ type MDStat struct { BlocksSyncedFinishTime float64 // current sync speed (in Kilobytes/sec) BlocksSyncedSpeed float64 - // Name of md component devices - Devices []string + // component devices + Devices []MDStatComponent } // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of @@ -82,30 +101,44 @@ func (fs FS) MDStat() ([]MDStat, error) { // parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of // structs containing the relevant info. func parseMDStat(mdStatData []byte) ([]MDStat, error) { + // TODO: + // - parse global hotspares from the "unused devices" line. mdStats := []MDStat{} lines := strings.Split(string(mdStatData), "\n") + knownRaidTypes := make(map[string]bool) for i, line := range lines { if strings.TrimSpace(line) == "" || line[0] == ' ' || - strings.HasPrefix(line, "Personalities") || strings.HasPrefix(line, "unused") { continue } + // Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10] + if len(knownRaidTypes) == 0 && strings.HasPrefix(line, personalitiesPrefix) { + personalities := strings.Fields(line[len(personalitiesPrefix):]) + for _, word := range personalities { + word := word[1 : len(word)-1] + knownRaidTypes[word] = true + } + continue + } deviceFields := strings.Fields(line) if len(deviceFields) < 3 { return nil, fmt.Errorf("%w: Expected 3+ lines, got %q", ErrFileParse, line) } mdName := deviceFields[0] // mdx - state := deviceFields[2] // active or inactive + state := deviceFields[2] // active, inactive, broken - mdType := "unknown" // raid1, raid5, etc. + mdType := "unknown" // raid1, raid5, etc. + var deviceStartIndex int if len(deviceFields) > 3 { // mdType may be in the 3rd or 4th field - if isRaidType(deviceFields[3]) { + if isRaidType(deviceFields[3], knownRaidTypes) { mdType = deviceFields[3] - } else if len(deviceFields) > 4 && isRaidType(deviceFields[4]) { + deviceStartIndex = 4 + } else if len(deviceFields) > 4 && isRaidType(deviceFields[4], knownRaidTypes) { // if the 3rd field is (...), the 4th field is the mdType mdType = deviceFields[4] + deviceStartIndex = 5 } } @@ -113,7 +146,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { return nil, fmt.Errorf("%w: Too few lines for md device: %q", ErrFileParse, mdName) } - // Failed disks have the suffix (F) & Spare disks have the suffix (S). + // Failed (Faulty) disks have the suffix (F) & Spare disks have the suffix (S). fail := int64(strings.Count(line, "(F)")) spare := int64(strings.Count(line, "(S)")) active, total, down, size, err := evalStatusLine(lines[i], lines[i+1]) @@ -160,6 +193,11 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { } } + devices, err := evalComponentDevices(deviceFields[deviceStartIndex:]) + if err != nil { + return nil, fmt.Errorf("error parsing components in md device %q: %w", mdName, err) + } + mdStats = append(mdStats, MDStat{ Name: mdName, Type: mdType, @@ -175,7 +213,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { BlocksSyncedPct: pct, BlocksSyncedFinishTime: finish, BlocksSyncedSpeed: speed, - Devices: evalComponentDevices(deviceFields), + Devices: devices, }) } @@ -185,11 +223,13 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { // check if a string's format is like the mdType // Rule 1: mdType should not be like (...) // Rule 2: mdType should not be like sda[0] -func isRaidType(mdType string) bool { - return !strings.ContainsAny(mdType, "([") +func isRaidType(mdType string, knownRaidTypes map[string]bool) bool { + _, ok := knownRaidTypes[mdType] + return !strings.ContainsAny(mdType, "([") && ok } func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) { + // e.g. 523968 blocks super 1.2 [4/4] [UUUU] statusFields := strings.Fields(statusLine) if len(statusFields) < 1 { return 0, 0, 0, 0, fmt.Errorf("%w: Unexpected statusline %q: %w", ErrFileParse, statusLine, err) @@ -280,17 +320,29 @@ func evalRecoveryLine(recoveryLine string) (blocksSynced int64, blocksToBeSynced return blocksSynced, blocksToBeSynced, pct, finish, speed, nil } -func evalComponentDevices(deviceFields []string) []string { - mdComponentDevices := make([]string, 0) - if len(deviceFields) > 3 { - for _, field := range deviceFields[4:] { - match := componentDeviceRE.FindStringSubmatch(field) - if match == nil { - continue - } - mdComponentDevices = append(mdComponentDevices, match[1]) +func evalComponentDevices(deviceFields []string) ([]MDStatComponent, error) { + mdComponentDevices := make([]MDStatComponent, 0) + for _, field := range deviceFields { + match := componentDeviceRE.FindStringSubmatch(field) + if match == nil { + continue } + descriptorIndex, err := strconv.ParseInt(match[2], 10, 32) + if err != nil { + return mdComponentDevices, fmt.Errorf("error parsing int from device %q: %w", match[2], err) + } + mdComponentDevices = append(mdComponentDevices, MDStatComponent{ + Name: match[1], + DescriptorIndex: int32(descriptorIndex), + // match may contain one or more of these + // https://github.com/torvalds/linux/blob/7ec462100ef9142344ddbf86f2c3008b97acddbe/drivers/md/md.c#L8376-L8392 + Faulty: strings.Contains(match[3], "(F)"), + Spare: strings.Contains(match[3], "(S)"), + Journal: strings.Contains(match[3], "(J)"), + Replacement: strings.Contains(match[3], "(R)"), + WriteMostly: strings.Contains(match[3], "(W)"), + }) } - return mdComponentDevices + return mdComponentDevices, nil } diff --git a/mdstat_test.go b/mdstat_test.go index c9d692b5..a2e8b1ab 100644 --- a/mdstat_test.go +++ b/mdstat_test.go @@ -26,6 +26,11 @@ func TestFS_MDStat(t *testing.T) { if err != nil { t.Fatalf("parsing of reference-file failed entirely: %s", err) } + // TODO: Test cases to capture in future: + // WriteMostly devices + // Journal devices + // Replacement devices + // Global hotspares refs := map[string]MDStat{ "md127": { @@ -43,7 +48,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdi2", "sdj2"}}, + Devices: []MDStatComponent{{Name: "sdi2", DescriptorIndex: 0}, {Name: "sdj2", DescriptorIndex: 1}}}, "md0": { Name: "md0", Type: "raid1", @@ -59,7 +64,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdi1", "sdj1"}}, + Devices: []MDStatComponent{{Name: "sdi1", DescriptorIndex: 0}, {Name: "sdj1", DescriptorIndex: 1}}}, "md4": { Name: "md4", Type: "raid1", @@ -75,7 +80,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sda3", "sdb3"}}, + Devices: []MDStatComponent{{Name: "sda3", Faulty: true, DescriptorIndex: 0}, {Name: "sdb3", Spare: true, DescriptorIndex: 1}}}, "md6": { Name: "md6", Type: "raid1", @@ -91,7 +96,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 8.5, BlocksSyncedFinishTime: 17, BlocksSyncedSpeed: 259783, - Devices: []string{"sdb2", "sdc", "sda2"}}, + Devices: []MDStatComponent{{Name: "sdb2", DescriptorIndex: 2, Faulty: true}, {Name: "sdc", DescriptorIndex: 1, Spare: true}, {Name: "sda2", DescriptorIndex: 0}}}, "md3": { Name: "md3", Type: "raid6", @@ -107,7 +112,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sda1", "sdh1", "sdg1", "sdf1", "sde1", "sdd1", "sdc1", "sdb1", "sdd1", "sdd2"}}, + Devices: []MDStatComponent{{Name: "sda1", DescriptorIndex: 8}, {Name: "sdh1", DescriptorIndex: 7}, {Name: "sdg1", DescriptorIndex: 6}, {Name: "sdf1", DescriptorIndex: 5}, {Name: "sde1", DescriptorIndex: 11}, {Name: "sdd1", DescriptorIndex: 3}, {Name: "sdc1", DescriptorIndex: 10}, {Name: "sdb1", DescriptorIndex: 9}, {Name: "sdd1", DescriptorIndex: 10, Spare: true}, {Name: "sdd2", DescriptorIndex: 11, Spare: true}}}, "md8": { Name: "md8", Type: "raid1", @@ -123,7 +128,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 8.5, BlocksSyncedFinishTime: 17, BlocksSyncedSpeed: 259783, - Devices: []string{"sdb1", "sda1", "sdc", "sde"}}, + Devices: []MDStatComponent{{Name: "sdb1", DescriptorIndex: 1}, {Name: "sda1", DescriptorIndex: 0}, {Name: "sdc", DescriptorIndex: 2, Spare: true}, {Name: "sde", DescriptorIndex: 3, Spare: true}}}, "md7": { Name: "md7", Type: "raid6", @@ -139,7 +144,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdb1", "sde1", "sdd1", "sdc1"}}, + Devices: []MDStatComponent{{Name: "sdb1", DescriptorIndex: 0}, {Name: "sde1", DescriptorIndex: 3}, {Name: "sdd1", DescriptorIndex: 2}, {Name: "sdc1", DescriptorIndex: 1, Faulty: true}}}, "md9": { Name: "md9", Type: "raid1", @@ -155,7 +160,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdc2", "sdd2", "sdb2", "sda2", "sde", "sdf", "sdg"}}, + Devices: []MDStatComponent{{Name: "sdc2", DescriptorIndex: 2}, {Name: "sdd2", DescriptorIndex: 3}, {Name: "sdb2", DescriptorIndex: 1}, {Name: "sda2", DescriptorIndex: 0}, {Name: "sde", DescriptorIndex: 4, Faulty: true}, {Name: "sdf", DescriptorIndex: 5, Faulty: true}, {Name: "sdg", DescriptorIndex: 6, Spare: true}}}, "md10": { Name: "md10", Type: "raid0", @@ -171,7 +176,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sda1", "sdb1"}}, + Devices: []MDStatComponent{{Name: "sda1", DescriptorIndex: 0}, {Name: "sdb1", DescriptorIndex: 1}}}, "md11": { Name: "md11", Type: "raid1", @@ -187,7 +192,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdb2", "sdc2", "sdc3", "hda", "ssdc2"}}, + Devices: []MDStatComponent{{Name: "sdb2", DescriptorIndex: 0}, {Name: "sdc2", DescriptorIndex: 1}, {Name: "sdc3", DescriptorIndex: 2, Faulty: true}, {Name: "hda", DescriptorIndex: 4, Spare: true}, {Name: "ssdc2", DescriptorIndex: 3, Spare: true}}}, "md12": { Name: "md12", Type: "raid0", @@ -203,7 +208,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdc2", "sdd2"}}, + Devices: []MDStatComponent{{Name: "sdc2", DescriptorIndex: 0}, {Name: "sdd2", DescriptorIndex: 1}}}, "md120": { Name: "md120", Type: "linear", @@ -219,7 +224,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sda1", "sdb1"}}, + Devices: []MDStatComponent{{Name: "sda1", DescriptorIndex: 1}, {Name: "sdb1", DescriptorIndex: 0}}}, "md126": { Name: "md126", Type: "raid0", @@ -235,7 +240,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdb", "sdc"}}, + Devices: []MDStatComponent{{Name: "sdb", DescriptorIndex: 1}, {Name: "sdc", DescriptorIndex: 0}}}, "md219": { Name: "md219", Type: "unknown", @@ -251,7 +256,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdc", "sda"}}, + Devices: []MDStatComponent{{Name: "sdb", DescriptorIndex: 2, Spare: true}, {Name: "sdc", DescriptorIndex: 1, Spare: true}, {Name: "sda", DescriptorIndex: 0, Spare: true}}}, "md00": { Name: "md00", Type: "raid0", @@ -267,7 +272,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"xvdb"}}, + Devices: []MDStatComponent{{Name: "xvdb", DescriptorIndex: 0}}}, "md101": { Name: "md101", Type: "raid0", @@ -283,7 +288,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 0, BlocksSyncedFinishTime: 0, BlocksSyncedSpeed: 0, - Devices: []string{"sdb", "sdd", "sdc"}}, + Devices: []MDStatComponent{{Name: "sdb", DescriptorIndex: 2}, {Name: "sdd", DescriptorIndex: 1}, {Name: "sdc", DescriptorIndex: 0}}}, "md201": { Name: "md201", Type: "raid1", @@ -299,7 +304,7 @@ func TestFS_MDStat(t *testing.T) { BlocksSyncedPct: 5.7, BlocksSyncedFinishTime: 0.2, BlocksSyncedSpeed: 114176, - Devices: []string{"sda3", "sdb3"}}, + Devices: []MDStatComponent{{Name: "sda3", DescriptorIndex: 0}, {Name: "sdb3", DescriptorIndex: 1}}}, } if want, have := len(refs), len(mdStats); want != have { @@ -314,18 +319,32 @@ func TestFS_MDStat(t *testing.T) { } func TestInvalidMdstat(t *testing.T) { - invalidMount := [][]byte{[]byte(` + invalidMount := [][]byte{ + // Test invalid Personality and format + []byte(` Personalities : [invalid] md3 : invalid 314159265 blocks 64k chunks unused devices: `), + // Test extra blank line []byte(` md12 : active raid0 sdc2[0] sdd2[1] 3886394368 blocks super 1.2 512k chunks -`)} +`), + // test for impossible component state + []byte(` +md127 : active raid1 sdi2[0] sdj2[1](Z) + 312319552 blocks [2/2] [UU] +`), + // test for malformed component state + []byte(` +md127 : active raid1 sdi2[0] sdj2[X] + 312319552 blocks [2/2] [UU] +`), + } for _, invalid := range invalidMount { _, err := parseMDStat(invalid)