From ceee8f8ad3c5845e2b09726d08b2fcd685805630 Mon Sep 17 00:00:00 2001 From: Andrei Aaron Date: Fri, 27 Feb 2026 11:18:16 +0000 Subject: [PATCH] Fix smartpqi arcconf parsing for mixed SSD/HDD arrays with 4K block sizes 1. Handle "4K Bytes" and other SI-suffixed block size tokens that caused strconv.Atoi failures on newer hardware. 2. Split multiple logical devices within a single arcconf output chunk. 3. Map new InterfaceType variants ("SATA SSD", "SAS 4K"). 4. Parse Channel and Protocol from physical device output. 5. Fix GetDiskType returning HDD for the first non-matching drive. Signed-off-by: Andrei Aaron --- smartpqi/arcconf.go | 146 ++++++++----- smartpqi/arcconf_test.go | 428 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 524 insertions(+), 50 deletions(-) diff --git a/smartpqi/arcconf.go b/smartpqi/arcconf.go index b68c185..92a2b24 100644 --- a/smartpqi/arcconf.go +++ b/smartpqi/arcconf.go @@ -16,24 +16,62 @@ const ( noArcConfRC = 127 ) -func parseLogicalDevices(rawData string) ([]LogicalDevice, error) { - logDevs := []LogicalDevice{} +// parseBlockSize converts a native disk block/sector size token from arcconf +// output into bytes. Known values are "512" (traditional) and "4K" (4K-native +// drives). This is not intended for parsing human-readable capacity fields +// like "SizeMB" which would require int64 for large disks. +func parseBlockSize(raw string) (int, error) { + switch strings.TrimSpace(raw) { + case "512": + return 512, nil + case "4K", "4k", "4096": + return 4096, nil + default: + return 0, fmt.Errorf("unsupported block size value %q", raw) + } +} - devices := strings.Split(rawData, "\n\n\n") - for _, device := range devices { - ldStart := strings.Index(device, "Logical Device number") - if ldStart >= 0 { - ldRawData := strings.TrimSpace(device[ldStart:]) +// splitLogicalDevices extracts individual logical device text blocks from raw +// arcconf output that may contain multiple LDs separated by varying amounts +// of whitespace (real hardware uses single blank lines between LDs). +func splitLogicalDevices(rawData string) []string { + const marker = "Logical Device number" + var blocks []string + + remaining := rawData + for { + idx := strings.Index(remaining, marker) + if idx < 0 { + break + } - // parse singular logical device - logDev, err := parseLogicalDeviceString(ldRawData) - if err != nil { - return []LogicalDevice{}, fmt.Errorf("error parsing logical device from arcconf getconfig ld: %s", err) - } + remaining = remaining[idx:] - logDevs = append(logDevs, logDev) + nextIdx := strings.Index(remaining[1:], marker) + if nextIdx >= 0 { + blocks = append(blocks, strings.TrimSpace(remaining[:nextIdx+1])) + remaining = remaining[nextIdx+1:] + } else { + blocks = append(blocks, strings.TrimSpace(remaining)) + break } } + + return blocks +} + +func parseLogicalDevices(rawData string) ([]LogicalDevice, error) { + logDevs := []LogicalDevice{} + + for _, ldBlock := range splitLogicalDevices(rawData) { + logDev, err := parseLogicalDeviceString(ldBlock) + if err != nil { + return []LogicalDevice{}, fmt.Errorf("error parsing logical device from arcconf getconfig ld: %s", err) + } + + logDevs = append(logDevs, logDev) + } + return logDevs, nil } @@ -118,8 +156,8 @@ func parseLogicalDeviceString(rawData string) (LogicalDevice, error) { case toks[0] == "Disk Name": // /dev/sdc (Disk0) (Bus: 1, Target: 0, Lun: 2)] ld.DiskName = strings.Fields(toks[1])[0] - case toks[0] == "Block Size of member drives": // 512 Bytes] - bs, err := strconv.Atoi(strings.Fields(toks[1])[0]) + case toks[0] == "Block Size of member drives": // 512 Bytes OR 4K Bytes] + bs, err := parseBlockSize(strings.Fields(toks[1])[0]) if err != nil { return ld, fmt.Errorf("failed to parse BlockSize from token '%s': %s", toks[1], err) } @@ -144,12 +182,14 @@ func parseLogicalDeviceString(rawData string) (LogicalDevice, error) { } ld.SizeMB = sizeMB - case toks[0] == "Interface Type": // Serial Attached SCSI] + case toks[0] == "Interface Type": switch toks[1] { - case "Serial Attached SCSI": + case "Serial Attached SCSI", "SAS 4K": ld.InterfaceType = "SCSI" - case "Serial Attached ATA": + case "Serial Attached ATA", "SATA SSD": ld.InterfaceType = "ATA" + default: + ld.InterfaceType = toks[1] } } } @@ -160,18 +200,22 @@ func parseLogicalDeviceString(rawData string) (LogicalDevice, error) { func parsePhysicalDevices(output string) ([]PhysicalDevice, error) { // list the physical device keys we're not parsing physicalDeviceParseKeys := map[string]bool{ - "Array": true, - "Block Size": true, - "Firmware": true, - "Model": true, - "Physical Block Size": true, - "Serial number": true, - "SSD": true, - "State": true, - "Total Size": true, - "Vendor": true, - "Write Cache": true, - } + "Array": true, + "Block Size": true, + "Firmware": true, + "Model": true, + "Physical Block Size": true, + "Serial number": true, + "SSD": true, + "State": true, + "Total Size": true, + "Transfer Speed": true, + "Negotiated Transfer Speed": true, + "Vendor": true, + "Write Cache": true, + } + + reportedChannelRe := regexp.MustCompile(`Reported Channel,Device\(T:L\)\s*:\s*(\d+),`) pDevs := []PhysicalDevice{} deviceStartRe := regexp.MustCompile(`Device\ #\d+`) devStartIdx := []int{} @@ -220,6 +264,15 @@ func parsePhysicalDevices(output string) ([]PhysicalDevice, error) { for _, lineRaw := range strings.Split(deviceLines[1], "\n") { line := strings.TrimSpace(lineRaw) + if m := reportedChannelRe.FindStringSubmatch(line); m != nil { + ch, err := strconv.Atoi(m[1]) + if err == nil { + pd.Channel = ch + } + + continue + } + // ignore lines that didn't have colon in it or have > 1 colon rawToks := strings.SplitN(line, ":", 2) if len(rawToks) < 2 || len(rawToks) > 2 { @@ -243,7 +296,7 @@ func parsePhysicalDevices(output string) ([]PhysicalDevice, error) { case toks[0] == "Block Size": dToks := strings.Split(toks[1], " ") - bSize, err := strconv.Atoi(dToks[0]) + bSize, err := parseBlockSize(dToks[0]) if err != nil { return []PhysicalDevice{}, fmt.Errorf("failed to parse Block Size from token %q: %s", dToks[0], err) } @@ -252,7 +305,7 @@ func parsePhysicalDevices(output string) ([]PhysicalDevice, error) { case toks[0] == "Physical Block Size": dToks := strings.Split(toks[1], " ") - bSize, err := strconv.Atoi(dToks[0]) + bSize, err := parseBlockSize(dToks[0]) if err != nil { return []PhysicalDevice{}, fmt.Errorf("failed to parse Physical Block Size from token %q: %s", dToks[0], err) } @@ -292,6 +345,8 @@ func parsePhysicalDevices(output string) ([]PhysicalDevice, error) { pd.SizeMB = bSize case toks[0] == "Write Cache": pd.WriteCache = toks[1] + case toks[0] == "Negotiated Transfer Speed" || toks[0] == "Transfer Speed": + pd.Protocol = strings.Fields(toks[1])[0] } } @@ -317,13 +372,9 @@ func parseGetConf(output string) ([]LogicalDevice, []PhysicalDevice, error) { return logDevs, phyDevs, fmt.Errorf("expected more than 3 lines of data in input") } - for _, device := range lines { - ldStart := strings.Index(device, "Logical Device number") - if ldStart >= 0 { - ldRawData := strings.TrimSpace(device[ldStart:]) - - // parse singular logical device - logDev, err := parseLogicalDeviceString(ldRawData) + for _, chunk := range lines { + for _, ldBlock := range splitLogicalDevices(chunk) { + logDev, err := parseLogicalDeviceString(ldBlock) if err != nil { return []LogicalDevice{}, []PhysicalDevice{}, fmt.Errorf("error parsing logical device from arcconf getconfig output: %s", err) } @@ -332,11 +383,10 @@ func parseGetConf(output string) ([]LogicalDevice, []PhysicalDevice, error) { } // all logical devices will have been parsed once we find the Physical Device Info section - pdStart := strings.Index(device, "Physical Device information") + pdStart := strings.Index(chunk, "Physical Device information") if pdStart >= 0 { - pdRawData := strings.TrimSpace(device[pdStart:]) + pdRawData := strings.TrimSpace(chunk[pdStart:]) - // parse all physical devices pDevs, err := parsePhysicalDevices(pdRawData) if err != nil { return []LogicalDevice{}, []PhysicalDevice{}, fmt.Errorf("error parsing physical device from arcconf getconfig output: %s", err) @@ -450,13 +500,17 @@ func (ac *arcConf) GetDiskType(path string) (disko.DiskType, error) { ctrl, err := ac.GetConfig(cID) if err != nil { errors = append(errors, fmt.Errorf("error while getting config for controller id:%d: %s", cID, err)) + continue } + for _, lDrive := range ctrl.LogicalDrives { - if lDrive.DiskName == path && lDrive.IsSSD() { - return disko.SSD, nil - } + if lDrive.DiskName == path { + if lDrive.IsSSD() { + return disko.SSD, nil + } - return disko.HDD, nil + return disko.HDD, nil + } } } diff --git a/smartpqi/arcconf_test.go b/smartpqi/arcconf_test.go index 27b9394..c1c04ce 100644 --- a/smartpqi/arcconf_test.go +++ b/smartpqi/arcconf_test.go @@ -851,6 +851,67 @@ Controller information Command completed successfully. ` +func TestParseBlockSize(t *testing.T) { + testCases := []struct { + input string + expected int + wantErr bool + }{ + {"512", 512, false}, + {"4096", 4096, false}, + {"4K", 4096, false}, + {"4k", 4096, false}, + {"", 0, true}, + {"abc", 0, true}, + {"1M", 0, true}, + {"1G", 0, true}, + } + + for _, tc := range testCases { + result, err := parseBlockSize(tc.input) + if tc.wantErr { + if err == nil { + t.Errorf("parseBlockSize(%q): expected error, got %d", tc.input, result) + } + continue + } + if err != nil { + t.Errorf("parseBlockSize(%q): unexpected error: %s", tc.input, err) + continue + } + if result != tc.expected { + t.Errorf("parseBlockSize(%q): got %d, expected %d", tc.input, result, tc.expected) + } + } +} + +var arcConfGetConfigLD4K = ` +Logical Device number 0 + Logical Device name : Logical Drive 1 + Disk Name : /dev/sda (Disk0) (Bus: 1, Target: 0, Lun: 0) + Block Size of member drives : 4K Bytes + Array : 0 + RAID level : 0 + Status of Logical Device : Optimal + Size : 572293 MB + Interface Type : Serial Attached SCSI +` + +func TestSmartPqiLogicalDevice4KBlockSize(t *testing.T) { + found, err := parseLogicalDevices(arcConfGetConfigLD4K) + if err != nil { + t.Errorf("failed to parse arcconf getconfig with 4K block size: %s", err) + return + } + if len(found) != 1 { + t.Errorf("expected 1 logical device, found %d", len(found)) + return + } + if found[0].BlockSize != 4096 { + t.Errorf("expected BlockSize 4096, got %d", found[0].BlockSize) + } +} + func TestSmartPqiList(t *testing.T) { found, err := parseList(arcList) if err != nil { @@ -1021,9 +1082,9 @@ Logical Device number 0 func TestArcconfParseConf(t *testing.T) { expectedPDs := []PhysicalDevice{} expectedPDsJSON := []string{ - `{"ArrayID":0,"Availability":"Online","BlockSize":512,"Channel":0,"ID":0,"Firmware":"5703","Model":"AL15SEB060N","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"63M0A0BYFJPF","SizeMB":572325,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}`, - `{"ArrayID":1,"Availability":"Online","BlockSize":512,"Channel":0,"ID":1,"Firmware":"5701","Model":"AL15SEB120N","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"59M0A06CFJRG","SizeMB":1144641,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}`, - `{"ArrayID":2,"Availability":"Online","BlockSize":512,"Channel":0,"ID":2,"Firmware":"CN03","Model":"ST1200MM0009","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"WFK076DT0000E821CET2","SizeMB":1144641,"Type":"HDD","Vendor":"SEAGATE","WriteCache":"Disabled (write-through)"}`, + `{"ArrayID":0,"Availability":"Online","BlockSize":512,"Channel":0,"ID":0,"Firmware":"5703","Model":"AL15SEB060N","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"63M0A0BYFJPF","SizeMB":572325,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}`, + `{"ArrayID":1,"Availability":"Online","BlockSize":512,"Channel":0,"ID":1,"Firmware":"5701","Model":"AL15SEB120N","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"59M0A06CFJRG","SizeMB":1144641,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}`, + `{"ArrayID":2,"Availability":"Online","BlockSize":512,"Channel":0,"ID":2,"Firmware":"CN03","Model":"ST1200MM0009","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"WFK076DT0000E821CET2","SizeMB":1144641,"Type":"HDD","Vendor":"SEAGATE","WriteCache":"Disabled (write-through)"}`, } for idx, content := range expectedPDsJSON { @@ -1119,7 +1180,7 @@ func TestSmartPqiNewController(t *testing.T) { t.Errorf("unexpected error creating new controller: %s", err) } - expectedJSON := `{"ID":1,"PhysicalDrives":{"0":{"ArrayID":0,"Availability":"Online","BlockSize":512,"Channel":0,"ID":0,"Firmware":"5703","Model":"AL15SEB060N","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"63M0A0BYFJPF","SizeMB":572325,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"},"1":{"ArrayID":1,"Availability":"Online","BlockSize":512,"Channel":0,"ID":1,"Firmware":"5701","Model":"AL15SEB120N","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"59M0A06CFJRG","SizeMB":1144641,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"},"2":{"ArrayID":2,"Availability":"Online","BlockSize":512,"Channel":0,"ID":2,"Firmware":"CN03","Model":"ST1200MM0009","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"WFK076DT0000E821CET2","SizeMB":1144641,"Type":"HDD","Vendor":"SEAGATE","WriteCache":"Disabled (write-through)"}},"LogicalDrives":{"0":{"ArrayID":0,"BlockSize":512,"Caching":"","Devices":[{"ArrayID":0,"Availability":"Online","BlockSize":512,"Channel":0,"ID":0,"Firmware":"5703","Model":"AL15SEB060N","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"63M0A0BYFJPF","SizeMB":572325,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}],"DiskName":"/dev/sdd","ID":0,"InterfaceType":"SCSI","Name":"Logical Drive 1","RAIDLevel":"0","SizeMB":572293},"1":{"ArrayID":1,"BlockSize":512,"Caching":"","Devices":[{"ArrayID":1,"Availability":"Online","BlockSize":512,"Channel":0,"ID":1,"Firmware":"5701","Model":"AL15SEB120N","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"59M0A06CFJRG","SizeMB":1144641,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}],"DiskName":"/dev/sde","ID":1,"InterfaceType":"SCSI","Name":"Logical Drive 2","RAIDLevel":"0","SizeMB":1144609},"2":{"ArrayID":2,"BlockSize":512,"Caching":"","Devices":[{"ArrayID":2,"Availability":"Online","BlockSize":512,"Channel":0,"ID":2,"Firmware":"CN03","Model":"ST1200MM0009","PhysicalBlockSize":512,"Protocol":"","SerialNumber":"WFK076DT0000E821CET2","SizeMB":1144641,"Type":"HDD","Vendor":"SEAGATE","WriteCache":"Disabled (write-through)"}],"DiskName":"/dev/sdc","ID":2,"InterfaceType":"SCSI","Name":"Logical Drive 3","RAIDLevel":"0","SizeMB":1144609}}}` + expectedJSON := `{"ID":1,"PhysicalDrives":{"0":{"ArrayID":0,"Availability":"Online","BlockSize":512,"Channel":0,"ID":0,"Firmware":"5703","Model":"AL15SEB060N","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"63M0A0BYFJPF","SizeMB":572325,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"},"1":{"ArrayID":1,"Availability":"Online","BlockSize":512,"Channel":0,"ID":1,"Firmware":"5701","Model":"AL15SEB120N","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"59M0A06CFJRG","SizeMB":1144641,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"},"2":{"ArrayID":2,"Availability":"Online","BlockSize":512,"Channel":0,"ID":2,"Firmware":"CN03","Model":"ST1200MM0009","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"WFK076DT0000E821CET2","SizeMB":1144641,"Type":"HDD","Vendor":"SEAGATE","WriteCache":"Disabled (write-through)"}},"LogicalDrives":{"0":{"ArrayID":0,"BlockSize":512,"Caching":"","Devices":[{"ArrayID":0,"Availability":"Online","BlockSize":512,"Channel":0,"ID":0,"Firmware":"5703","Model":"AL15SEB060N","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"63M0A0BYFJPF","SizeMB":572325,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}],"DiskName":"/dev/sdd","ID":0,"InterfaceType":"SCSI","Name":"Logical Drive 1","RAIDLevel":"0","SizeMB":572293},"1":{"ArrayID":1,"BlockSize":512,"Caching":"","Devices":[{"ArrayID":1,"Availability":"Online","BlockSize":512,"Channel":0,"ID":1,"Firmware":"5701","Model":"AL15SEB120N","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"59M0A06CFJRG","SizeMB":1144641,"Type":"HDD","Vendor":"TOSHIBA","WriteCache":"Disabled (write-through)"}],"DiskName":"/dev/sde","ID":1,"InterfaceType":"SCSI","Name":"Logical Drive 2","RAIDLevel":"0","SizeMB":1144609},"2":{"ArrayID":2,"BlockSize":512,"Caching":"","Devices":[{"ArrayID":2,"Availability":"Online","BlockSize":512,"Channel":0,"ID":2,"Firmware":"CN03","Model":"ST1200MM0009","PhysicalBlockSize":512,"Protocol":"SAS","SerialNumber":"WFK076DT0000E821CET2","SizeMB":1144641,"Type":"HDD","Vendor":"SEAGATE","WriteCache":"Disabled (write-through)"}],"DiskName":"/dev/sdc","ID":2,"InterfaceType":"SCSI","Name":"Logical Drive 3","RAIDLevel":"0","SizeMB":1144609}}}` expCtrl := Controller{} if err := json.Unmarshal([]byte(expectedJSON), &expCtrl); err != nil { t.Errorf("failed to unmarshal expected Controller JSON: %s", err) @@ -1130,6 +1191,365 @@ func TestSmartPqiNewController(t *testing.T) { } } +// arcconf GETCONFIG output with mixed SSD/HDD arrays and 4K native sector drives. +// Confidential identifiers (serial numbers, WWNs) replaced with synthetic values. +// Unparsed sections (sensors, SPDM, error counters, phy info) trimmed for clarity. +// This tests that the parser handles multiple LDs in the same \n\n\n chunk. +var arcConfGetConfigMixedArrays = ` +Controllers found: 1 +---------------------------------------------------------------------- +Controller information +---------------------------------------------------------------------- + Controller Status : Optimal + Controller Mode : Mixed + Controller Model : Cisco 24G TriMode M1 RAID 4GB FBWC 16D UCSC-RAID-M1L16 + Driver Name : smartpqi + Connector #0 + Connector name : CN2 + + + Connector #1 + Connector name : CN3 + + + +---------------------------------------------------------------------- +Array Information +---------------------------------------------------------------------- +Array Number 0 + Name : A + Interface : SATA SSD + Block Size : 512 Bytes + Device 0 : Present (915715MB, SATA, SSD, Connector:CN2, Backplane:0, Slot:1) SN_SSD_0 + + +Array Number 1 + Name : B + Interface : SAS 4K + Block Size : 4K Bytes + Device 1 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:2) SN_HDD_1 + Device 2 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:3) SN_HDD_2 + Device 3 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:4) SN_HDD_3 + Device 4 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:6) SN_HDD_4 + Device 5 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:7) SN_HDD_5 + Device 8 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:5) SN_HDD_8 + + + +-------------------------------------------------------- +Logical device information +-------------------------------------------------------- +Logical Device number 0 + Logical Device name : RAID0_1 + Disk Name : /dev/sdb (Disk0) (Bus: 1, Target: 0, Lun: 0) + Block Size of member drives : 512 Bytes + Array : 0 + RAID level : 0 + Status of Logical Device : Optimal + Size : 915527 MB + Interface Type : SATA SSD + Device 0 : Present (915715MB, SATA, SSD, Connector:CN2, Backplane:0, Slot:1) SN_SSD_0 + +Logical Device number 1 + Logical Device name : RAID0_234567 + Disk Name : /dev/sda (Disk0) (Bus: 1, Target: 0, Lun: 1) + Block Size of member drives : 4K Bytes + Array : 1 + RAID level : 0 + Status of Logical Device : Optimal + Size : 13732910 MB + Interface Type : SAS 4K + Device 1 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:2) SN_HDD_1 + Device 8 : Present (2289272MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:5) SN_HDD_8 + + +---------------------------------------------------------------------- +Physical Device information +---------------------------------------------------------------------- + Channel #0: + Device #0 + Device is a Hard drive + State : Online + Block Size : 512 Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SATA 6.0 Gb/s + Reported Channel,Device(T:L) : 0,0(0:0) + Array : 0 + Vendor : ATA + Model : Micron_5300_MTFDDAK960TDS + Firmware : D3MC000 + Serial number : SN_SSD_0 + Total Size : 915715 MB + Write Cache : Disabled (write-through) + SSD : Yes + + Device #1 + Device is a Hard drive + State : Online + Block Size : 4K Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,1(1:0) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB24EP + Firmware : 5704 + Serial number : SN_HDD_1 + Total Size : 2289272 MB + Write Cache : Disabled (write-through) + SSD : No + + Device #2 + Device is a Hard drive + State : Online + Block Size : 4K Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,2(2:0) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB24EP + Firmware : 5704 + Serial number : SN_HDD_2 + Total Size : 2289272 MB + Write Cache : Enabled + SSD : No + + Device #3 + Device is a Hard drive + State : Online + Block Size : 4K Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,3(3:0) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB24EP + Firmware : 5704 + Serial number : SN_HDD_3 + Total Size : 2289272 MB + Write Cache : Disabled (write-through) + SSD : No + + Device #4 + Device is a Hard drive + State : Online + Block Size : 4K Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,4(4:0) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB24EP + Firmware : 5704 + Serial number : SN_HDD_4 + Total Size : 2289272 MB + Write Cache : Disabled (write-through) + SSD : No + + Device #5 + Device is a Hard drive + State : Online + Block Size : 4K Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,5(5:0) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB24EP + Firmware : 5704 + Serial number : SN_HDD_5 + Total Size : 2289272 MB + Write Cache : Disabled (write-through) + SSD : No + + Device #8 + Device is a Hard drive + State : Online + Block Size : 4K Bytes + Physical Block Size : 4K Bytes + Negotiated Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,8(8:0) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB24EP + Firmware : 5704 + Serial number : SN_HDD_8 + Total Size : 2289272 MB + Write Cache : Disabled (write-through) + SSD : No + + Channel #2: + Device #0 + Device is an Enclosure Services Device + Reported Channel,Device(T:L) : 2,0(0:0) + Enclosure ID : 0 + Type : SES2 + Vendor : Cisco + + Backplane: + Device #0 + Device is an UBM Controller + Backplane ID : 0 + Type : UBM + Firmware : 0.2 (dec), 0.2 (hex) + + +---------------------------------------------------------------------- +maxCache information +---------------------------------------------------------------------- + No maxCache Array found + + +Command completed successfully. +` + +func TestSmartPqiGetConfigMixedArrays(t *testing.T) { + ctrl, err := newController(1, arcConfGetConfigMixedArrays) + if err != nil { + t.Fatalf("failed to parse arcconf getconfig: %s", err) + } + + // --- Logical Drives --- + + expectedLDs := []struct { + id int + name string + diskName string + blockSize int + arrayID int + raidLevel string + sizeMB int + interfaceType string + isSSD bool + numDevices int + }{ + {0, "RAID0_1", "/dev/sdb", 512, 0, "0", 915527, "ATA", true, 1}, + {1, "RAID0_234567", "/dev/sda", 4096, 1, "0", 13732910, "SCSI", false, 6}, + } + + if len(ctrl.LogicalDrives) != len(expectedLDs) { + t.Fatalf("expected %d logical drives, got %d", len(expectedLDs), len(ctrl.LogicalDrives)) + } + + for _, exp := range expectedLDs { + ld := ctrl.LogicalDrives[exp.id] + if ld == nil { + t.Fatalf("missing logical drive %d", exp.id) + } + if ld.ID != exp.id { + t.Errorf("LD%d ID: got %d, want %d", exp.id, ld.ID, exp.id) + } + if ld.Name != exp.name { + t.Errorf("LD%d Name: got %q, want %q", exp.id, ld.Name, exp.name) + } + if ld.DiskName != exp.diskName { + t.Errorf("LD%d DiskName: got %q, want %q", exp.id, ld.DiskName, exp.diskName) + } + if ld.BlockSize != exp.blockSize { + t.Errorf("LD%d BlockSize: got %d, want %d", exp.id, ld.BlockSize, exp.blockSize) + } + if ld.ArrayID != exp.arrayID { + t.Errorf("LD%d ArrayID: got %d, want %d", exp.id, ld.ArrayID, exp.arrayID) + } + if ld.RAIDLevel != exp.raidLevel { + t.Errorf("LD%d RAIDLevel: got %q, want %q", exp.id, ld.RAIDLevel, exp.raidLevel) + } + if ld.SizeMB != exp.sizeMB { + t.Errorf("LD%d SizeMB: got %d, want %d", exp.id, ld.SizeMB, exp.sizeMB) + } + if ld.InterfaceType != exp.interfaceType { + t.Errorf("LD%d InterfaceType: got %q, want %q", exp.id, ld.InterfaceType, exp.interfaceType) + } + if ld.IsSSD() != exp.isSSD { + t.Errorf("LD%d IsSSD: got %v, want %v", exp.id, ld.IsSSD(), exp.isSSD) + } + if len(ld.Devices) != exp.numDevices { + t.Errorf("LD%d linked devices: got %d, want %d", exp.id, len(ld.Devices), exp.numDevices) + } + } + + // --- Physical Drives --- + + expectedPDs := []struct { + id int + channel int + arrayID int + availability string + blockSize int + physicalBlockSize int + protocol string + vendor string + model string + firmware string + serialNumber string + sizeMB int + mediaType MediaType + writeCache string + }{ + {0, 0, 0, "Online", 512, 4096, "SATA", "ATA", "Micron_5300_MTFDDAK960TDS", "D3MC000", "SN_SSD_0", 915715, SSD, "Disabled (write-through)"}, + {1, 0, 1, "Online", 4096, 4096, "SAS", "TOSHIBA", "AL15SEB24EP", "5704", "SN_HDD_1", 2289272, HDD, "Disabled (write-through)"}, + {2, 0, 1, "Online", 4096, 4096, "SAS", "TOSHIBA", "AL15SEB24EP", "5704", "SN_HDD_2", 2289272, HDD, "Enabled"}, + {3, 0, 1, "Online", 4096, 4096, "SAS", "TOSHIBA", "AL15SEB24EP", "5704", "SN_HDD_3", 2289272, HDD, "Disabled (write-through)"}, + {4, 0, 1, "Online", 4096, 4096, "SAS", "TOSHIBA", "AL15SEB24EP", "5704", "SN_HDD_4", 2289272, HDD, "Disabled (write-through)"}, + {5, 0, 1, "Online", 4096, 4096, "SAS", "TOSHIBA", "AL15SEB24EP", "5704", "SN_HDD_5", 2289272, HDD, "Disabled (write-through)"}, + {8, 0, 1, "Online", 4096, 4096, "SAS", "TOSHIBA", "AL15SEB24EP", "5704", "SN_HDD_8", 2289272, HDD, "Disabled (write-through)"}, + } + + if len(ctrl.PhysicalDrives) != len(expectedPDs) { + t.Fatalf("expected %d physical drives, got %d", len(expectedPDs), len(ctrl.PhysicalDrives)) + } + + for _, exp := range expectedPDs { + pd := ctrl.PhysicalDrives[exp.id] + if pd == nil { + t.Fatalf("missing physical drive %d", exp.id) + } + if pd.ID != exp.id { + t.Errorf("PD%d ID: got %d, want %d", exp.id, pd.ID, exp.id) + } + if pd.Channel != exp.channel { + t.Errorf("PD%d Channel: got %d, want %d", exp.id, pd.Channel, exp.channel) + } + if pd.ArrayID != exp.arrayID { + t.Errorf("PD%d ArrayID: got %d, want %d", exp.id, pd.ArrayID, exp.arrayID) + } + if pd.Availability != exp.availability { + t.Errorf("PD%d Availability: got %q, want %q", exp.id, pd.Availability, exp.availability) + } + if pd.BlockSize != exp.blockSize { + t.Errorf("PD%d BlockSize: got %d, want %d", exp.id, pd.BlockSize, exp.blockSize) + } + if pd.PhysicalBlockSize != exp.physicalBlockSize { + t.Errorf("PD%d PhysicalBlockSize: got %d, want %d", exp.id, pd.PhysicalBlockSize, exp.physicalBlockSize) + } + if pd.Protocol != exp.protocol { + t.Errorf("PD%d Protocol: got %q, want %q", exp.id, pd.Protocol, exp.protocol) + } + if pd.Vendor != exp.vendor { + t.Errorf("PD%d Vendor: got %q, want %q", exp.id, pd.Vendor, exp.vendor) + } + if pd.Model != exp.model { + t.Errorf("PD%d Model: got %q, want %q", exp.id, pd.Model, exp.model) + } + if pd.Firmware != exp.firmware { + t.Errorf("PD%d Firmware: got %q, want %q", exp.id, pd.Firmware, exp.firmware) + } + if pd.SerialNumber != exp.serialNumber { + t.Errorf("PD%d SerialNumber: got %q, want %q", exp.id, pd.SerialNumber, exp.serialNumber) + } + if pd.SizeMB != exp.sizeMB { + t.Errorf("PD%d SizeMB: got %d, want %d", exp.id, pd.SizeMB, exp.sizeMB) + } + if pd.Type != exp.mediaType { + t.Errorf("PD%d Type: got %v, want %v", exp.id, pd.Type, exp.mediaType) + } + if pd.WriteCache != exp.writeCache { + t.Errorf("PD%d WriteCache: got %q, want %q", exp.id, pd.WriteCache, exp.writeCache) + } + } +} + func TestSmartPqiNewControllerBadInput(t *testing.T) { var inputEmpty = `` var inputEmptyLines = `