From 9d696ffcae03ce16add98eda249a72ef4b81d545 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Mon, 12 Feb 2024 14:02:13 -0600 Subject: [PATCH 1/3] lint: bump golanglint-ci to latest 1.56.1 Older golanglint-ci attempted to lint imported modules due to newer golang version (1.20+), moving to 1.56.1 resolves the issue but then made other modules be come much more difficult to deal with. I noticed there are quite a few //nolint: lines throughout the project and instead of continuing to pepper those around, I chose to disable not-useful nagging/hard-to-configure linters. Signed-off-by: Ryan Harper --- .golangci.yml | 11 ----------- Makefile | 2 +- disk_test.go | 8 ++++---- linux/root_test.go | 13 ++++++++----- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b815d4f..cf63b93 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,21 +1,12 @@ -linters-settings: - wsl: - allow-cuddle-declarations: true - funlen: - lines: 80 - statements: 60 - linters: # you can see what is disabled with: golangci-lint linters enable: - bodyclose - - depguard - dogsled - dupl - errcheck - exhaustive - exportloopref - - funlen - gochecknoglobals - gochecknoinits - goconst @@ -27,7 +18,6 @@ linters: - gosimple - govet - ineffassign - - lll - misspell - nakedret - noctx @@ -40,7 +30,6 @@ linters: - unparam - unused - whitespace - - wsl issues: exclude-rules: diff --git a/Makefile b/Makefile index 24bc3d9..6d4af76 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LDFLAGS := "${ldflags:+$ldflags }-X main.version=${ver}${suff}" BUILD_FLAGS := -ldflags "-X main.version=$(VERSION_FULL)" ENV_ROOT := $(shell [ "$$(id -u)" = "0" ] && echo env || echo sudo ) -GOLANGCI_VER = v1.52.2 +GOLANGCI_VER = v1.56.1 GOLANGCI = ./tools/golangci-lint-$(GOLANGCI_VER) CMDS := demo/demo ptimg/ptimg diff --git a/disk_test.go b/disk_test.go index 3b90f3d..4308bc2 100644 --- a/disk_test.go +++ b/disk_test.go @@ -57,8 +57,8 @@ func TestDiskString(t *testing.T) { Type: disko.HDD, Attachment: disko.ATA, Partitions: disko.PartitionSet{ - 1: {Start: 3 * mib, Last: 253*mib - 1, Number: 1}, - 3: {Start: 500 * mib, Last: 600*mib - 1, Number: 3}, + 1: disko.Partition{Start: 3 * mib, Last: 253*mib - 1, Number: 1}, + 3: disko.Partition{Start: 500 * mib, Last: 600*mib - 1, Number: 3}, }, UdevInfo: disko.UdevInfo{}, } @@ -93,9 +93,9 @@ func TestDiskDetails(t *testing.T) { Type: disko.HDD, Attachment: disko.ATA, Partitions: disko.PartitionSet{ - 1: {Start: 3 * mib, Last: 253*mib - 1, Number: 1, + 1: disko.Partition{Start: 3 * mib, Last: 253*mib - 1, Number: 1, Name: "my-name", Type: partid.LinuxLVM}, - 2: {Start: 253 * mib, Last: 400*mib - 1, Number: 2, + 2: disko.Partition{Start: 253 * mib, Last: 400*mib - 1, Number: 2, Type: disko.PartType(myType)}, }, UdevInfo: disko.UdevInfo{}, diff --git a/linux/root_test.go b/linux/root_test.go index 9174650..66d837f 100644 --- a/linux/root_test.go +++ b/linux/root_test.go @@ -18,8 +18,11 @@ import ( "machinerun.io/disko/partid" ) -const MiB = 1024 * 1024 -const GiB = MiB * 1024 +const ( + MiB = 1024 * 1024 + GiB = MiB * 1024 + dtLoopPrefix = "detach loop " +) // runLog - run command and Printf, useful for debugging errors. func runLog(args ...string) { @@ -112,7 +115,7 @@ func TestRootPartition(t *testing.T) { runLog("losetup", "-a") t.Fatalf("failed loop: %s\n", err) } else { - cl.AddF(cleanup, "detach loop "+tmpFile) + cl.AddF(cleanup, dtLoopPrefix+tmpFile) loopDev = path } @@ -210,7 +213,7 @@ func TestRootPartitionUpdate(t *testing.T) { runLog("losetup", "-a") t.Fatalf("failed loop: %s\n", err) } else { - cl.AddF(cleanup, "detach loop "+tmpFile) + cl.AddF(cleanup, dtLoopPrefix+tmpFile) loopDev = path } @@ -297,7 +300,7 @@ func TestRootPartitionDelete(t *testing.T) { runLog("losetup", "-a") t.Fatalf("failed loop: %s\n", err) } else { - cl.AddF(cleanup, "detach loop "+tmpFile) + cl.AddF(cleanup, dtLoopPrefix+tmpFile) loopDev = path } From a6012d5b4db0c8539721ad20bf48dd735f0d62e1 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Thu, 8 Feb 2024 17:14:11 -0600 Subject: [PATCH 2/3] Add RAID Controller interface to enable multiple controllers Introduce a RAIDController interface to abstract linux/system from having to know which raid controller it is interacting with. This also supports handling a raid interface with multiple controllers. - Update build Makefile target to depend on all go files Signed-off-by: Ryan Harper --- Makefile | 2 +- demo/megaraid.go | 3 +- go.mod | 1 + go.sum | 2 + linux/raidcontroller.go | 16 ++++++++ linux/system.go | 49 ++++++++++++------------ linux/util.go | 77 ++++++++++++++++++++++++++++++++++++++ megaraid/linux.go | 83 ----------------------------------------- megaraid/megaraid.go | 11 ++++++ megaraid/storcli.go | 42 +++++++++++++++++++++ 10 files changed, 178 insertions(+), 108 deletions(-) create mode 100644 linux/raidcontroller.go diff --git a/Makefile b/Makefile index 6d4af76..7e072b6 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ all: build check build: .build $(CMDS) -.build: $(GO_FILES) +.build: $(ALL_GO_FILES) go build ./... @touch $@ diff --git a/demo/megaraid.go b/demo/megaraid.go index e9db809..88b22de 100644 --- a/demo/megaraid.go +++ b/demo/megaraid.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/urfave/cli/v2" + "machinerun.io/disko/linux" "machinerun.io/disko/megaraid" ) @@ -69,7 +70,7 @@ func megaraidDiskSummary(c *cli.Context) error { } path := "" - if bname, err := megaraid.NameByDiskID(d.ID); err == nil { + if bname, err := linux.NameByDiskID(mraid.DriverSysfsPath(), d.ID); err == nil { path = "/dev/" + bname } diff --git a/go.mod b/go.mod index 9ff4d53..8d0ce63 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,5 @@ require ( github.com/stretchr/testify v1.8.2 github.com/urfave/cli/v2 v2.25.3 golang.org/x/sys v0.8.0 + machinerun.io/disko v0.0.13 // indirect ) diff --git a/go.sum b/go.sum index d1efc79..c06a8a0 100644 --- a/go.sum +++ b/go.sum @@ -634,6 +634,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +machinerun.io/disko v0.0.13 h1:1kkOorxGvLprdTSksh7fOsNK0WZ0Lq+4/ZQgPeuXnsY= +machinerun.io/disko v0.0.13/go.mod h1:qorFih3sB8bmNcHWHyjXujgYfaugOjHj+s2GdK33+lk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/linux/raidcontroller.go b/linux/raidcontroller.go new file mode 100644 index 0000000..c26fe3c --- /dev/null +++ b/linux/raidcontroller.go @@ -0,0 +1,16 @@ +package linux + +import "machinerun.io/disko" + +type RAIDControllerType string + +const ( + MegaRAIDControllerType RAIDControllerType = "megaraid" +) + +type RAIDController interface { + // Type() RAIDControllerType + GetDiskType(string) (disko.DiskType, error) + IsSysPathRAID(string) bool + DriverSysfsPath() string +} diff --git a/linux/system.go b/linux/system.go index 1274135..6418788 100644 --- a/linux/system.go +++ b/linux/system.go @@ -16,13 +16,15 @@ import ( ) type linuxSystem struct { - megaraid megaraid.MegaRaid + raidctrls []RAIDController } // System returns an linux specific implementation of disko.System interface. func System() disko.System { return &linuxSystem{ - megaraid: megaraid.CachingStorCli(), + raidctrls: []RAIDController{ + megaraid.CachingStorCli(), + }, } } @@ -154,15 +156,22 @@ func (ls *linuxSystem) ScanDisk(devicePath string) (disko.Disk, error) { return disko.Disk{}, err } - diskType, err = ls.getDiskType(devicePath, udInfo) - if err != nil { - return disko.Disk{}, err - } - attachType = getAttachType(udInfo) - if megaraid.IsMegaRaidSysPath(udInfo.Properties["DEVPATH"]) { - attachType = disko.RAID + for _, ctrl := range ls.raidctrls { + if IsSysPathRAID(udInfo.Properties["DEVPATH"], ctrl.DriverSysfsPath()) { + // we know this is device is part of a raid, so if we cannot get + // disk type we must return an error + dType, err := ctrl.GetDiskType(devicePath) + if err != nil { + return disko.Disk{}, fmt.Errorf("failed to get diskType of %q from RAID controller: %s", devicePath, err) + } + + attachType = disko.RAID + diskType = dType + + break + } } ro, err = getDiskReadOnly(name) @@ -277,22 +286,16 @@ func (ls *linuxSystem) Wipe(d disko.Disk) error { return udevSettle() } -func (ls *linuxSystem) getDiskType(path string, udInfo disko.UdevInfo) (disko.DiskType, error) { - ctrl, err := ls.megaraid.Query(0) - if err == nil { - for _, vd := range ctrl.VirtDrives { - if vd.Path == path { - if ctrl.DriveGroups[vd.DriveGroup].IsSSD() { - return disko.SSD, nil - } - - return disko.HDD, nil +func (ls *linuxSystem) GetDiskType(path string, udInfo disko.UdevInfo) (disko.DiskType, error) { + for _, ctrl := range ls.raidctrls { + if IsSysPathRAID(udInfo.Properties["DEVPATH"], ctrl.DriverSysfsPath()) { + dType, err := ctrl.GetDiskType(path) + if err != nil { + return disko.HDD, fmt.Errorf("failed to get diskType of %q from RAID controller: %s", path, err) } + + return dType, nil } - } else if err != megaraid.ErrNoStorcli && err != megaraid.ErrNoController && - err != megaraid.ErrUnsupported { - return disko.HDD, err } - return getDiskType(udInfo) } diff --git a/linux/util.go b/linux/util.go index e9b5e99..23e3577 100644 --- a/linux/util.go +++ b/linux/util.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "strconv" "strings" "syscall" @@ -254,3 +255,79 @@ func Floor(val, unit uint64) uint64 { return (val / unit) * unit } + +// IsSysPathRAID - is this sys path (udevadm info's DEVPATH) on a scsi controller. +// +// syspath will look something like +// /devices/pci0000:3a/0000:3a:02.0/0000:3c:00.0/host0/target0:2:2/0:2:2:0/block/sdc +func IsSysPathRAID(syspath string, driverSysPath string) bool { + if !strings.HasPrefix(syspath, "/sys") { + syspath = "/sys" + syspath + } + + if !strings.Contains(syspath, "/host") { + return false + } + + fp, err := filepath.EvalSymlinks(syspath) + if err != nil { + fmt.Printf("seriously? %s\n", err) + return false + } + + for _, path := range GetSysPaths(driverSysPath) { + if strings.HasPrefix(fp, path) { + return true + } + } + + return false +} + +// NameByDiskID - return the linux name (sda) for the disk with given DiskID +func NameByDiskID(driverSysPath string, id int) (string, error) { + // given ID, we expect a single file in: + // /0000:05:00.0/host0/target0:0:/0:0::0/block/ + // Note: This does not work for some controllers such as a MegaRAID SAS3508 + // See https://github.com/project-machine/disko/issues/101 + idStr := fmt.Sprintf("%d", id) + blkDir := driverSysPath + "/*/host*/target0:0:" + idStr + "/0:0:" + idStr + ":0/block/*" + matches, err := filepath.Glob(blkDir) + + if err != nil { + return "", err + } + + if len(matches) != 1 { + return "", fmt.Errorf("found %d matches to %s", len(matches), blkDir) + } + + return path.Base(matches[0]), nil +} + +func GetSysPaths(driverSysPath string) []string { + paths := []string{} + // a raid driver has directory entries for each of the scsi hosts on that controller. + // $cd /sys/bus/pci/drivers/ + // $ for d in *; do [ -d "$d" ] || continue; echo "$d -> $( cd "$d" && pwd -P )"; done + // 0000:3c:00.0 -> /sys/devices/pci0000:3a/0000:3a:02.0/0000:3c:00.0 + // module -> /sys/module/ + + // We take a hack path and consider anything with a ":" in that dir as a host path. + matches, err := filepath.Glob(driverSysPath + "/*:*") + + if err != nil { + fmt.Printf("errors: %s\n", err) + return paths + } + + for _, p := range matches { + fp, err := filepath.EvalSymlinks(p) + + if err == nil { + paths = append(paths, fp) + } + } + + return paths +} diff --git a/megaraid/linux.go b/megaraid/linux.go index e813710..cbd974a 100644 --- a/megaraid/linux.go +++ b/megaraid/linux.go @@ -1,86 +1,3 @@ package megaraid -import ( - "fmt" - "path" - "path/filepath" - "strings" -) - const sysDriverMegaRaidSAS = "/sys/bus/pci/drivers/megaraid_sas" - -// IsMegaRaidSysPath - is this sys path (udevadm info's DEVPATH) on a megaraid controller. -// -// syspath will look something like -// /devices/pci0000:3a/0000:3a:02.0/0000:3c:00.0/host0/target0:2:2/0:2:2:0/block/sdc -func IsMegaRaidSysPath(syspath string) bool { - if !strings.HasPrefix(syspath, "/sys") { - syspath = "/sys" + syspath - } - - if !strings.Contains(syspath, "/host") { - return false - } - - fp, err := filepath.EvalSymlinks(syspath) - if err != nil { - fmt.Printf("seriously? %s\n", err) - return false - } - - for _, path := range getSysPaths() { - if strings.HasPrefix(fp, path) { - return true - } - } - - return false -} - -// NameByDiskID - return the linux name (sda) for the disk with given DiskID -func NameByDiskID(id int) (string, error) { - // given ID, we expect a single file in: - // /0000:05:00.0/host0/target0:0:/0:0::0/block/ - // Note: This does not work for some megaraid controllers such as SAS3508 - // See https://machinerun.io/disko/issues/101 - idStr := fmt.Sprintf("%d", id) - blkDir := sysDriverMegaRaidSAS + "/*/host*/target0:0:" + idStr + "/0:0:" + idStr + ":0/block/*" - matches, err := filepath.Glob(blkDir) - - if err != nil { - return "", err - } - - if len(matches) != 1 { - return "", fmt.Errorf("found %d matches to %s", len(matches), blkDir) - } - - return path.Base(matches[0]), nil -} - -func getSysPaths() []string { - paths := []string{} - // sysDriverMegaRaidSAS has directory entries for each of the scsi hosts on that controller. - // $cd /sys/bus/pci/drivers/megaraid_sas - // $ for d in *; do [ -d "$d" ] || continue; echo "$d -> $( cd "$d" && pwd -P )"; done - // 0000:3c:00.0 -> /sys/devices/pci0000:3a/0000:3a:02.0/0000:3c:00.0 - // module -> /sys/module/megaraid_sas - - // We take a hack path and consider anything with a ":" in that dir as a host path. - matches, err := filepath.Glob(sysDriverMegaRaidSAS + "/*:*") - - if err != nil { - fmt.Printf("errors: %s\n", err) - return paths - } - - for _, p := range matches { - fp, err := filepath.EvalSymlinks(p) - - if err == nil { - paths = append(paths, fp) - } - } - - return paths -} diff --git a/megaraid/megaraid.go b/megaraid/megaraid.go index fa1fc7f..e0d0d2f 100644 --- a/megaraid/megaraid.go +++ b/megaraid/megaraid.go @@ -3,6 +3,8 @@ package megaraid import ( "encoding/json" "errors" + + "machinerun.io/disko" ) // Controller - a Megaraid controller @@ -131,6 +133,15 @@ func (t MediaType) MarshalJSON() ([]byte, error) { type MegaRaid interface { // Query - Query the controller provided Query(int) (Controller, error) + + // GetDiskType - Determine the disk type if controller owns disk + GetDiskType(string) (disko.DiskType, error) + + // DriverSysfsPath - Return the sysfs path to the linux driver for this controller + DriverSysfsPath() string + + // IsSysPathRAID - Check if sysfs path is a device on the controller + IsSysPathRAID(string) bool } // ErrNoController - Error reported by Query if no controller is found. diff --git a/megaraid/storcli.go b/megaraid/storcli.go index da81c9b..88cfdc3 100644 --- a/megaraid/storcli.go +++ b/megaraid/storcli.go @@ -11,6 +11,7 @@ import ( "time" "github.com/patrickmn/go-cache" + "machinerun.io/disko" ) type storCli struct { @@ -74,6 +75,19 @@ func (sc *storCli) Query(cID int) (Controller, error) { return newController(cID, cxDxOut, cxVxOut) } +func (sc *storCli) DriverSysfsPath() string { + return SysfsPCIDriversPath +} + +func (sc *storCli) GetDiskType(path string) (disko.DiskType, error) { + return disko.HDD, fmt.Errorf("missing controller to run query") +} + +// not implemented in driver layer +func (sc *storCli) IsSysPathRAID(syspath string) bool { + return false +} + func newController(cID int, cxDxOut string, cxVxOut string) (Controller, error) { const pathPropName = "OS Drive Name" @@ -607,3 +621,31 @@ func (csc *cachingStorCli) Query(cID int) (Controller, error) { return ctrl, err } + +func (csc *cachingStorCli) GetDiskType(path string) (disko.DiskType, error) { + ctrl, err := csc.Query(0) + if err == nil { + for _, vd := range ctrl.VirtDrives { + if vd.Path == path { + if ctrl.DriveGroups[vd.DriveGroup].IsSSD() { + return disko.SSD, nil + } + + return disko.HDD, nil + } + } + } else if err != ErrNoStorcli && err != ErrNoController && err != ErrUnsupported { + return disko.HDD, err + } + + return disko.HDD, fmt.Errorf("cannot determine disk type") +} + +func (csc *cachingStorCli) DriverSysfsPath() string { + return csc.mr.DriverSysfsPath() +} + +// not implemented in the driver layer +func (csc *cachingStorCli) IsSysPathRAID(syspath string) bool { + return false +} From eabe011097817e39b4c3adaa7adfe51e8bfe4641 Mon Sep 17 00:00:00 2001 From: Ryan Harper Date: Sat, 10 Feb 2024 19:13:27 -0600 Subject: [PATCH 3/3] Add support for smartpqi/arcconf RAID adapters on Linux The smartpqi driver for Adaptec/Microsemi RAID controllers uses a cli tool called `arcconf`. Add support modeled after the existing MegaRAID/storcli structures and implement the RAID Controller interface introduced to abstract the specific RAID adapter for the linux system. - Did not implement the Caching mechansim, repeated invocations do not have any significant impact on system, 5 minute timeout seems quite long to wait for RAID device changes which happen immediately after issuing arcconf commands. - Refactored how linux package looks up disks to see if they belong to a RAID adapter to prevent import loops. - Added tests for arcconf for the interface commands parsing output collected from systems with smartpqi driver and card - Added smartpqi to the demo program Signed-off-by: Ryan Harper --- demo/main.go | 1 + demo/smartpqi.go | 117 ++++ go.mod | 1 - go.sum | 2 - linux/raidcontroller.go | 1 + linux/system.go | 2 + megaraid/linux.go | 2 +- smartpqi/arcconf.go | 568 +++++++++++++++++++ smartpqi/arcconf_test.go | 1159 ++++++++++++++++++++++++++++++++++++++ smartpqi/linux.go | 3 + smartpqi/smartpqi.go | 139 +++++ 11 files changed, 1991 insertions(+), 4 deletions(-) create mode 100644 demo/smartpqi.go create mode 100644 smartpqi/arcconf.go create mode 100644 smartpqi/arcconf_test.go create mode 100644 smartpqi/linux.go create mode 100644 smartpqi/smartpqi.go diff --git a/demo/main.go b/demo/main.go index e3859a2..58fd0da 100644 --- a/demo/main.go +++ b/demo/main.go @@ -48,6 +48,7 @@ func main() { Commands: []*cli.Command{ &diskCommands, &megaraidCommands, + &smartpqiCommands, &lvmCommands, &miscCommands, }, diff --git a/demo/smartpqi.go b/demo/smartpqi.go new file mode 100644 index 0000000..5d1e218 --- /dev/null +++ b/demo/smartpqi.go @@ -0,0 +1,117 @@ +package main + +import ( + "encoding/json" + "fmt" + "strconv" + + "github.com/urfave/cli/v2" + "machinerun.io/disko/smartpqi" +) + +//nolint:gochecknoglobals +var smartpqiCommands = cli.Command{ + Name: "smartpqi", + Usage: "smartpqi / arcconf commands", + Subcommands: []*cli.Command{ + { + Name: "dump", + Usage: "Dump information about smartpqi", + Action: smartpqiDump, + }, + { + Name: "disk-summary", + Usage: "Show information about virtual devices on system", + Action: smartpqiDiskSummary, + }, + { + Name: "list-controllers", + Usage: "Show the discovered controller IDs", + Action: smartpqiListControllers, + }, + }, +} + +func smartpqiListControllers(c *cli.Context) error { + arc := smartpqi.ArcConf() + ctrls, err := arc.List() + if err != nil { + return fmt.Errorf("failed to list controllers: %s", err) + } + + fmt.Printf("Found %d controllers.", len(ctrls)) + for _, cID := range ctrls { + fmt.Printf("Controller ID: %d\n", cID) + } + return nil +} + +func smartpqiDiskSummary(c *cli.Context) error { + var err error + var ctrlNum = 1 + var ctrlArg = c.Args().First() + + if ctrlArg != "" { + ctrlNum, err = strconv.Atoi(ctrlArg) + if err != nil { + return fmt.Errorf("could not convert to integer: %s", err) + } + } + + arc := smartpqi.ArcConf() + ctrl, err := arc.Query(ctrlNum) + + if err != nil { + return err + } + + data := [][]string{{"Path", "Name", "DiskType", "RAID"}} + + for _, ld := range ctrl.LogicalDrives { + stype := "HDD" + + if ld.IsSSD() { + stype = "SSD" + } + + name := ld.Name + if ld.Name == "" { + name = fmt.Sprintf("logicalid-%d", ld.ID) + } + + data = append(data, []string{ld.DiskName, name, stype, ld.RAIDLevel}) + } + + printTextTable(data) + + return nil +} + +func smartpqiDump(c *cli.Context) error { + var err error + var ctrlNum = 1 + var ctrlArg = c.Args().First() + + if ctrlArg != "" { + ctrlNum, err = strconv.Atoi(ctrlArg) + if err != nil { + return fmt.Errorf("could not convert to integer: %s", err) + } + } + + arc := smartpqi.ArcConf() + ctrl, err := arc.Query(ctrlNum) + + if err != nil { + return err + } + + jbytes, err := json.MarshalIndent(&ctrl, "", " ") + if err != nil { + return err + } + + fmt.Printf("%s\n", string(jbytes)) + + return nil +} diff --git a/go.mod b/go.mod index 8d0ce63..9ff4d53 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,4 @@ require ( github.com/stretchr/testify v1.8.2 github.com/urfave/cli/v2 v2.25.3 golang.org/x/sys v0.8.0 - machinerun.io/disko v0.0.13 // indirect ) diff --git a/go.sum b/go.sum index c06a8a0..d1efc79 100644 --- a/go.sum +++ b/go.sum @@ -634,8 +634,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -machinerun.io/disko v0.0.13 h1:1kkOorxGvLprdTSksh7fOsNK0WZ0Lq+4/ZQgPeuXnsY= -machinerun.io/disko v0.0.13/go.mod h1:qorFih3sB8bmNcHWHyjXujgYfaugOjHj+s2GdK33+lk= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/linux/raidcontroller.go b/linux/raidcontroller.go index c26fe3c..476abdf 100644 --- a/linux/raidcontroller.go +++ b/linux/raidcontroller.go @@ -6,6 +6,7 @@ type RAIDControllerType string const ( MegaRAIDControllerType RAIDControllerType = "megaraid" + SmartPqiControllerType RAIDControllerType = "smartpqi" ) type RAIDController interface { diff --git a/linux/system.go b/linux/system.go index 6418788..2d4b0e7 100644 --- a/linux/system.go +++ b/linux/system.go @@ -13,6 +13,7 @@ import ( "golang.org/x/sys/unix" "machinerun.io/disko" "machinerun.io/disko/megaraid" + "machinerun.io/disko/smartpqi" ) type linuxSystem struct { @@ -24,6 +25,7 @@ func System() disko.System { return &linuxSystem{ raidctrls: []RAIDController{ megaraid.CachingStorCli(), + smartpqi.ArcConf(), }, } } diff --git a/megaraid/linux.go b/megaraid/linux.go index cbd974a..c82861b 100644 --- a/megaraid/linux.go +++ b/megaraid/linux.go @@ -1,3 +1,3 @@ package megaraid -const sysDriverMegaRaidSAS = "/sys/bus/pci/drivers/megaraid_sas" +const SysfsPCIDriversPath = "/sys/bus/pci/drivers/megaraid_sas" diff --git a/smartpqi/arcconf.go b/smartpqi/arcconf.go new file mode 100644 index 0000000..b68c185 --- /dev/null +++ b/smartpqi/arcconf.go @@ -0,0 +1,568 @@ +package smartpqi + +import ( + "bytes" + "fmt" + "os/exec" + "regexp" + "strconv" + "strings" + "syscall" + + "machinerun.io/disko" +) + +const ( + noArcConfRC = 127 +) + +func parseLogicalDevices(rawData string) ([]LogicalDevice, error) { + logDevs := []LogicalDevice{} + + 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:]) + + // 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) + } + + logDevs = append(logDevs, logDev) + } + } + return logDevs, nil +} + +func parseLogicalDeviceString(rawData string) (LogicalDevice, error) { + // list the logical device keys we're not parsing at this time + logicalDeviceSkipKeys := map[string]bool{ + "Device ID": true, + "Last Consistency Check Completion Time": true, + "Last Consistency Check Duration": true, + "Status of Logical Device": true, + "Stripe-unit size": true, + "Full Stripe Size": true, + "Device Type": true, // Data] + "Boot Type": true, // Primary and Secondary] + "Heads": true, // 255] + "Sectors Per Track": true, // 32] + "Cylinders": true, // 65535] + "Caching": true, // Enabled] + "Mount Points": true, // Not Mounted] + "LD Acceleration Method": true, // Controller Cache] + "SED Encryption": true, // Disabled] + "Volume Unique Identifier": true, // 600508B1001C6DB81E7099960E5B5796] + "Consistency Check Status": true, // Not Applicable] + } + ld := LogicalDevice{} + + if len(rawData) < 1 { + return ld, fmt.Errorf("cannot parse an empty string") + } + + // break data into first line which has the logical device number and + // the remaining data is in mostly key : value pairs + lineData := strings.SplitN(rawData, "\n", 2) + if len(lineData) != 2 { + return ld, fmt.Errorf("expected exactly 2 lines of data, found %d in %q", len(lineData), lineData) + } + + // extract the LogicalDrive ID from the first token + // Logical Device number N + toks := strings.Split(strings.TrimSpace(lineData[0]), " ") + if len(toks) != 4 { + return ld, fmt.Errorf("expected 4 tokens in %q, found %d", lineData[0], len(toks)) + } + + ldID, err := strconv.Atoi(toks[len(toks)-1]) + if err != nil { + return LogicalDevice{}, fmt.Errorf("error while parsing integer from %q: %s", toks[len(toks)-1], err) + } + + ld.ID = ldID + + // split the remainder into line data, split on :, but expect 2 tokens + // since some lines have ':' in the value portion + for _, lineRaw := range strings.Split(lineData[1], "\n") { + // remove leading space formatting + line := strings.TrimSpace(lineRaw) + + // ignore lines that didn't have colon in it + rawToks := strings.SplitN(line, ":", 2) + if len(rawToks) < 2 { + continue + } + + // the raw tokens split on colon will have whitespace, so let's trim + // that into our final key, value tokens: + // 'Logical Device Name ', 'LogicalDrv 0' => 'Logical Device Name', 'LogicalDrv 0' + toks := []string{} + for _, tok := range rawToks { + toks = append(toks, strings.TrimSpace(tok)) + } + + // skip tokens if key is on the ignore list + if _, ok := logicalDeviceSkipKeys[toks[0]]; ok { + continue + } + + // map the key to LogicalDevice member field + switch { + case toks[0] == "Logical Device name": // LogicalDrv 0] + ld.Name = toks[1] + + 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]) + if err != nil { + return ld, fmt.Errorf("failed to parse BlockSize from token '%s': %s", toks[1], err) + } + + ld.BlockSize = bs + case toks[0] == "Array": // 2] + aID, err := strconv.Atoi(toks[1]) + if err != nil { + return ld, fmt.Errorf("failed to parse ArrayID from token '%s': %s", toks[1], err) + } + + ld.ArrayID = aID + case toks[0] == "RAID level": // 0] + // we don't parse RAIDLevel as integer since arrconf device support + // non-numeric values like: 0, 1, 1Triple, 10, 10Triple, 5, 6, 50 and 60 + ld.RAIDLevel = toks[1] + + case toks[0] == "Size": // 1144609 MB] + sizeMB, err := strconv.Atoi(strings.Fields(toks[1])[0]) + if err != nil { + return ld, fmt.Errorf("failed to parse Size from token '%s': %s", toks[1], err) + } + + ld.SizeMB = sizeMB + case toks[0] == "Interface Type": // Serial Attached SCSI] + switch toks[1] { + case "Serial Attached SCSI": + ld.InterfaceType = "SCSI" + case "Serial Attached ATA": + ld.InterfaceType = "ATA" + } + } + } + + return ld, nil +} + +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, + } + pDevs := []PhysicalDevice{} + deviceStartRe := regexp.MustCompile(`Device\ #\d+`) + devStartIdx := []int{} + devEndIdx := []int{} + + devStart := deviceStartRe.FindAllIndex([]byte(output), -1) + if len(devStart) < 1 { + return []PhysicalDevice{}, fmt.Errorf("error finding start of PhysicalDevice in data") + } + + // construct pairs of start and stop points in the string marking the + // beginning and end of a single PhysicalDevice entry + for idx, devIdx := range devStart { + devStartIdx = append(devStartIdx, devIdx[0]) + + if idx > 0 { + // 0, 1 + devEndIdx = append(devEndIdx, devIdx[0]-1) + } + } + devEndIdx = append(devEndIdx, len(output)) + + deviceRaw := []string{} + for idx, devStart := range devStartIdx { + devEnd := devEndIdx[idx] + deviceRaw = append(deviceRaw, strings.TrimSpace(output[devStart:devEnd])) + } + + for _, deviceStr := range deviceRaw { + deviceLines := strings.SplitN(deviceStr, "\n", 2) + if len(deviceLines) < 2 { + return []PhysicalDevice{}, fmt.Errorf("expected more than 2 lines of data, found %d", len(deviceRaw)) + } + + toks := strings.Split(deviceLines[0], "#") + pdID, err := strconv.Atoi(toks[1]) + + if err != nil { + return []PhysicalDevice{}, fmt.Errorf("error parsing PhysicalDevice device id in %q: %s", toks[1], err) + } + + pd := PhysicalDevice{ + ID: pdID, + } + + for _, lineRaw := range strings.Split(deviceLines[1], "\n") { + line := strings.TrimSpace(lineRaw) + + // 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 { + continue + } + + // the raw tokens split on colon will have whitespace, so let's trim + // that into our final key, value tokens: + // 'Logical Device Name ', 'LogicalDrv 0' => 'Logical Device Name', 'LogicalDrv 0' + toks := []string{} + for _, tok := range rawToks { + toks = append(toks, strings.TrimSpace(tok)) + } + + // skip tokens if key is not in the parse list + if _, ok := physicalDeviceParseKeys[toks[0]]; !ok { + continue + } + + switch { + case toks[0] == "Block Size": + dToks := strings.Split(toks[1], " ") + + bSize, err := strconv.Atoi(dToks[0]) + if err != nil { + return []PhysicalDevice{}, fmt.Errorf("failed to parse Block Size from token %q: %s", dToks[0], err) + } + + pd.BlockSize = bSize + case toks[0] == "Physical Block Size": + dToks := strings.Split(toks[1], " ") + + bSize, err := strconv.Atoi(dToks[0]) + if err != nil { + return []PhysicalDevice{}, fmt.Errorf("failed to parse Physical Block Size from token %q: %s", dToks[0], err) + } + + pd.PhysicalBlockSize = bSize + case toks[0] == "Array": + aID, err := strconv.Atoi(toks[1]) + if err != nil { + return []PhysicalDevice{}, fmt.Errorf("failed to parse Array from token %q: %s", toks[1], err) + } + + pd.ArrayID = aID + case toks[0] == "Vendor": + pd.Vendor = toks[1] + case toks[0] == "Model": + pd.Model = toks[1] + case toks[0] == "Firmware": + pd.Firmware = toks[1] + case toks[0] == "Serial number": + pd.SerialNumber = toks[1] + case toks[0] == "SSD": + if toks[1] == "Yes" { + pd.Type = SSD + } else { + pd.Type = HDD + } + case toks[0] == "State": + pd.Availability = toks[1] + case toks[0] == "Total Size": + dToks := strings.Split(toks[1], " ") + bSize, err := strconv.Atoi(dToks[0]) + + if err != nil { + return []PhysicalDevice{}, fmt.Errorf("failed to parse Total Size from token %q: %s", dToks[0], err) + } + + pd.SizeMB = bSize + case toks[0] == "Write Cache": + pd.WriteCache = toks[1] + } + } + + // ignore any Device that doesn't have a Availability/State + if len(pd.Availability) > 0 { + pDevs = append(pDevs, pd) + } + } + + return pDevs, nil +} + +func parseGetConf(output string) ([]LogicalDevice, []PhysicalDevice, error) { + logDevs := []LogicalDevice{} + phyDevs := []PhysicalDevice{} + + if len(output) < 1 { + return logDevs, phyDevs, fmt.Errorf("cannot parse an empty string") + } + + lines := strings.Split(output, "\n\n\n") + if len(lines) < 3 { + 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) + if err != nil { + return []LogicalDevice{}, []PhysicalDevice{}, fmt.Errorf("error parsing logical device from arcconf getconfig output: %s", err) + } + + logDevs = append(logDevs, logDev) + } + + // all logical devices will have been parsed once we find the Physical Device Info section + pdStart := strings.Index(device, "Physical Device information") + if pdStart >= 0 { + pdRawData := strings.TrimSpace(device[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) + } + + phyDevs = pDevs + } + } + + return logDevs, phyDevs, nil +} + +// parseList - parse arcconf list command output +func parseList(output string) ([]int, error) { + var numCtrlRe = regexp.MustCompile(`(?m)Controllers found:\s\d+`) + var ctrlRe = regexp.MustCompile(`(?m)Controller\s\d+`) + var numCtrls int + var controllers []int + + // extract the number of controllers that arcconf reported + numCtrlsStr := numCtrlRe.FindString(output) + if len(numCtrlsStr) == 0 { + return controllers, fmt.Errorf("error parsing arcconf output, missing 'Controllers found:' output") + } + + toks := strings.Split(numCtrlsStr, " ") + if len(toks) != 3 { + return controllers, fmt.Errorf("error parsing arcconf output, found %d tokens expected 3", len(toks)) + } + + numCtrls, err := strconv.Atoi(toks[2]) + if err != nil { + return controllers, fmt.Errorf("failed to parse int from %q: %s", toks[2], err) + } + + // extract the Controller Ids listed + for _, match := range ctrlRe.FindAllString(output, -1) { + toks := strings.Split(match, " ") + if len(toks) == 2 { + ctrlID, err := strconv.Atoi(toks[1]) + if err != nil { + return controllers, fmt.Errorf("failed to parse int from %q: %s", toks[1], err) + } + + controllers = append(controllers, ctrlID) + } + } + + if len(controllers) != numCtrls { + return []int{}, fmt.Errorf("mismatched output, found %d controllers, expected %d", len(controllers), numCtrls) + } + + return controllers, nil +} + +type arcConf struct { +} + +// arcConf returns a arcconf specific implementation of SmartPqi interface +func ArcConf() SmartPqi { + return &arcConf{} +} + +// SmartPqi Interface Implementation +func (ac *arcConf) List() ([]int, error) { + var stdout, stderr []byte + var rc int + var controllerIDs []int + + args := []string{"list", "nologs"} + if stdout, stderr, rc = arcconf(args...); rc != 0 { + var err error = ErrNoArcconf + if rc != noArcConfRC { + err = cmdError(args, stdout, stderr, rc) + } + + return controllerIDs, err + } + + controllerIDs, err := parseList(string(stdout)) + if err != nil { + return controllerIDs, fmt.Errorf("failed to parse arcconf output: %s", err) + } + + return controllerIDs, nil +} + +func (ac *arcConf) Query(cID int) (Controller, error) { + ctrlIDs, err := ac.List() + if err != nil { + return Controller{}, fmt.Errorf("failed to enumerate controllers: %s", err) + } + + for _, ctrlID := range ctrlIDs { + if ctrlID == cID { + return ac.GetConfig(cID) + } + } + + return Controller{}, fmt.Errorf("unknown controller id %d", cID) +} + +func (ac *arcConf) GetDiskType(path string) (disko.DiskType, error) { + cIDs, err := ac.List() + if err != nil { + return disko.HDD, fmt.Errorf("failed to enumerate controllers: %s", err) + } + + errors := []error{} + for _, cID := range cIDs { + ctrl, err := ac.GetConfig(cID) + if err != nil { + errors = append(errors, fmt.Errorf("error while getting config for controller id:%d: %s", cID, err)) + } + for _, lDrive := range ctrl.LogicalDrives { + if lDrive.DiskName == path && lDrive.IsSSD() { + return disko.SSD, nil + } + + return disko.HDD, nil + } + } + + for _, err := range errors { + if err != ErrNoArcconf && err != ErrNoController && err != ErrUnsupported { + return disko.HDD, err + } + } + + return disko.HDD, fmt.Errorf("cannot determine disk type") +} + +func (ac *arcConf) DriverSysfsPath() string { + return SysfsPCIDriversPath +} + +// not implemented at the driver level +func (ac *arcConf) IsSysPathRAID(path string) bool { + return false +} + +func (ac *arcConf) GetConfig(cID int) (Controller, error) { + var stdout, stderr []byte + var rc int + + // getconfig ID + args := []string{"getconfig", fmt.Sprintf("%d", cID), "nologs"} + if stdout, stderr, rc = arcconf(args...); rc != 0 { + var err error = ErrNoArcconf + if rc != noArcConfRC { + err = cmdError(args, stdout, stderr, rc) + } + + return Controller{}, err + } + + getConfigOut := string(stdout) + + return newController(cID, getConfigOut) +} + +func newController(cID int, arcGetConfigOut string) (Controller, error) { + ctrl := Controller{ + ID: cID, + } + + lDevs, pDevs, err := parseGetConf(arcGetConfigOut) + if err != nil { + return Controller{}, fmt.Errorf("failed to parse arcconf getconfig output: %s", err) + } + + // PD.ID -> PhysicalDevice + // type DriveSet map[int]*PhysicalDevice + ctrl.PhysicalDrives = DriveSet{} + ctrl.LogicalDrives = LogicalDriveSet{} + + for idx := range pDevs { + pDev := pDevs[idx] + ctrl.PhysicalDrives[pDev.ID] = &pDev + } + + for lIdx := range lDevs { + lDev := lDevs[lIdx] + ctrl.LogicalDrives[lDev.ID] = &lDev + for idx := range pDevs { + pDev := pDevs[idx] + if lDev.ArrayID == pDev.ArrayID { + lDev.Devices = append(lDev.Devices, &pDev) + } + } + } + + return ctrl, nil +} + +func arcconf(args ...string) ([]byte, []byte, int) { + cmd := exec.Command("arcconf", args...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Run() + + return stdout.Bytes(), stderr.Bytes(), getCommandErrorRCDefault(err, noArcConfRC) +} + +func cmdError(args []string, out []byte, err []byte, rc int) error { + if rc == 0 { + return nil + } + + return fmt.Errorf( + "command failed [%d]:\n cmd: %v\n out:%s\n err:%s", + rc, args, out, err) +} + +func getCommandErrorRCDefault(err error, rcError int) int { + if err == nil { + return 0 + } + + exitError, ok := err.(*exec.ExitError) + if ok { + if status, ok := exitError.Sys().(syscall.WaitStatus); ok { + return status.ExitStatus() + } + } + + return rcError +} diff --git a/smartpqi/arcconf_test.go b/smartpqi/arcconf_test.go new file mode 100644 index 0000000..27b9394 --- /dev/null +++ b/smartpqi/arcconf_test.go @@ -0,0 +1,1159 @@ +package smartpqi + +import ( + "encoding/json" + "reflect" + "strings" + "testing" +) + +var arcConfGetConfig = ` +Controllers found: 1 +---------------------------------------------------------------------- +Controller information +---------------------------------------------------------------------- + Controller Status : Optimal + Controller Mode : Mixed + Channel description : SCSI + Controller Model : Cisco 24G TriMode M1 RAID 4GB FBWC 16D UCSC-RAID-M1L16 + Vendor ID : 0x9005 + Device ID : 0x028F + Subsystem Vendor ID : 0x1137 + Subsystem Device ID : 0x02F9 + Controller Serial Number : 3137F30003A + Controller World Wide Name : 50000D1E01787B00 + Physical Slot : 16 + Temperature : 33 C/ 91 F (Normal) + Negotiated PCIe Data Rate : PCIe 4.0 x16(31504 MB/s) + PCI Address (Domain:Bus:Device:Function) : 0:4b:0:0 + Number of Ports : 2 + Internal Port Count : 2 + External Port Count : 0 + Defunct disk drive count : 0 + NCQ status : Enabled + Queue Depth : Automatic + Monitor and Performance Delay : 60 minutes + Elevator Sort : Enabled + Degraded Mode Performance Optimization : Disabled + Latency : Disabled + Post Prompt Timeout : 0 seconds + Boot Controller : False + Primary Boot Volume : Logical device 2(Logical Drive 3) + Secondary Boot Volume : Logical device 2(Logical Drive 3) + Driver Name : smartpqi + Driver Supports SSD I/O Bypass : Yes + NVMe Supported : Yes + NVMe Configuration Supported : Yes + Manufacturing Part Number : Not Applicable + Manufacturing Spare Part Number : Not Applicable + Manufacturing Wellness Log : Not Applicable + Manufacturing SKU Number : Not Applicable + Manufacturing Model : Not Applicable + NVRAM Checksum Status : Passed + Sanitize Lock Setting : None + Expander Minimum Scan Duration : 0 seconds + Expander Scan Time-out : 350 seconds + Active PCIe Maximum Read Request Size : 2048 bytes + Pending PCIe Maximum Read Request Size : Not Applicable + PCIe Maximum Payload Size : 512 bytes + Persistent Event Log Policy : Oldest + UEFI Health Reporting Mode : Enabled + Reboot Required Reasons : Not Available + ------------------------------------------------------------------- + Power Settings + ------------------------------------------------------------------- + Power Consumption : 16891 milliWatts + Current Power Mode : Maximum Performance + Pending Power Mode : Not Applicable + Survival Mode : Enabled + ------------------------------------------------------------------- + Cache Properties + ------------------------------------------------------------------- + Cache Status : Temporarily Disabled + Cache State : Disabled Flashlight Capacitor Charge Is Low + Cache State Details : Temporarily Disabled(Reason: Charge level of capacitor attached to cache module is low) + Cache Serial Number : Not Applicable + Cache memory : 3644 MB + Read Cache Percentage : 10 percent + Write Cache Percentage : 90 percent + No-Battery Write Cache : Disabled + Wait for Cache Room : Disabled + Write Cache Bypass Threshold Size : 1040 KB + ------------------------------------------------------------------- + Green Backup Information + ------------------------------------------------------------------- + Backup Power Status : Not Fully Charged + Battery/Capacitor Pack Count : 1 + Hardware Error : No Error + Power Type : Supercap + Current Temperature : 19 deg C + Maximum Temperature : 60 deg C + Threshold Temperature : 50 deg C + Voltage : 3885 milliVolts + Maximum Voltage : 5208 milliVolts + Current : 0 milliAmps + Health Status : 100 percent + Relative Charge : 0 percent + ------------------------------------------------------------------- + Physical Drive Write Cache Policy Information + ------------------------------------------------------------------- + Configured Drives : Default + Unconfigured Drives : Default + HBA Drives : Default + ------------------------------------------------------------------- + maxCache Properties + ------------------------------------------------------------------- + maxCache Version : 4 + maxCache RAID5 WriteBack Enabled : Enabled + ------------------------------------------------------------------- + RAID Properties + ------------------------------------------------------------------- + Logical devices/Failed/Degraded : 3/0/0 + Spare Activation Mode : Failure + Background consistency check : Idle + Consistency Check Delay : 3 seconds + Parallel Consistency Check Supported : Enabled + Parallel Consistency Check Count : 1 + Inconsistency Repair Policy : Disabled + Consistency Check Inconsistency Notify : Disabled + Rebuild Priority : High + Expand Priority : Medium + ------------------------------------------------------------------- + Controller Version Information + ------------------------------------------------------------------- + Firmware : 03.01.24.082-p + Driver : Linux 2.1.12-055 + Hardware Revision : B + Hardware Minor Revision : 1 + SEEPROM Version : 0 + CPLD Revision : 1 + ------------------------------------------------------------------- + SED Encryption Properties + ------------------------------------------------------------------- + SED Encryption : Off + Key Mode : None + SED Encryption Status : Not Applicable + SED Operation In Progress : Not Applicable + Master Key Identifier : Not Applicable + SED Controller Password Status : Not Configured + Countdown Timer : Not Applicable + Attempts Left : Not Applicable + ------------------------------------------------------------------- + Controller maxCrypto Information + ------------------------------------------------------------------- + maxCrypto Supported : Yes + maxCrypto Status : Disabled + License Installed : Not Installed + Express Local maxCrypto : Not Configured + Controller Password : Not Configured + Crypto Officer Password : Not Configured + User Password : Not Configured + Allow New Plaintext Logical device(s) : Not Applicable + Key Management Mode : Not Configured + Master Key : Not Configured + Remote Mode Master Key Mismatch : No + Master Key Reset in Progress : No + Local Key Cache : Not Configured + FW Locked for Update : No + Controller Locked : No + Has Suspended Controller Password : No + Logical Drive(s) Locked For Missing Controller Password : No + Password Recovery Parameters Set : No + SSD I/O Bypass Mixing : Supported + maxCache Mixing : Supported + Skip Controller Password : Disabled + Controller Password Unlock Attempts Remaining : 0 + Crypto Account Password Unlock Attempts Remaining : 0 + User Account Password Unlock Attempts Remaining : 0 + Number of maxCrypto Physical devices : 0 + Number of maxCrypto Data Logical devices : 0 + Number of maxCrypto Foreign Logical devices without key : 0 + Number of maxCrypto Logical devices with maxCrypto off : 0 + ------------------------------------------------------------------- + Temperature Sensors Information + ------------------------------------------------------------------- + Sensor ID : 0 + Current Value : 21 deg C + Max Value Since Powered On : 21 deg C + Location : Inlet Ambient + + Sensor ID : 1 + Current Value : 33 deg C + Max Value Since Powered On : 33 deg C + Location : ASIC + + Sensor ID : 2 + Current Value : 27 deg C + Max Value Since Powered On : 27 deg C + Location : Top + + Sensor ID : 3 + Current Value : 26 deg C + Max Value Since Powered On : 26 deg C + Location : Bottom + + ------------------------------------------------------------------- + Out Of Band Interface Settings + ------------------------------------------------------------------- + OOB Interface : MCTP + Pending OOB Interface : MCTP + I2C Address : 0xDE + Pending I2C Address : 0xDE + ------------------------------------------------------------------- + PBSI + ------------------------------------------------------------------- + I2C Clock Speed : Not Applicable + I2C Clock Stretching : Not Applicable + Pending I2C Clock Speed : Not Applicable + Pending I2C Clock Stretching : Not Applicable + ------------------------------------------------------------------- + MCTP + ------------------------------------------------------------------- + SMBus Device Type : Fixed + SMBus Channel : Disabled + Static EIDs Use On Initialization : Disabled + VDM Notification : Enabled + Pending SMBus Device Type : Fixed + Pending SMBus Channel : Disabled + Pending Static EIDs Use On Initialization : Disabled + Pending VDM Notification : Enabled + ------------------------------------------------------------------- + Controller SPDM Setting Information + ------------------------------------------------------------------- + Version : 0x10 + Endpoint ID : 0x09 + Crypto Timeout Exponent : 20 + Authority Key ID : DA:20:0B:C9:6B:E0:81:DC:C5:D8:96:00:CD:0E:3C:F7:59:DB:3C:A0 + Slot 0 : Valid and Sealed + Slot 1 : Available + Slot 2 : Available + Slot 3 : Available + Slot 4 : Available + Slot 5 : Available + Slot 6 : Available + Slot 7 : Available + ------------------------------------------------------------------- + Capabilities + ------------------------------------------------------------------- + Digests And Certificate : Supported + Challenge : Supported + Measurements With Signature : Supported + ------------------------------------------------------------------- + Connector information + ------------------------------------------------------------------- + Connector #0 + Connector name : CN2 + Connection Number : 0 + Functional Mode : Mixed + Connector Location : Internal + SAS Address : 50000D1E01787B00 + Current Discovery Protocol : UBM + Pending Discovery Protocol : Not Applicable + + + Connector #1 + Connector name : CN3 + Connection Number : 1 + Functional Mode : Mixed + Connector Location : Internal + SAS Address : 50000D1E01787B0C + Current Discovery Protocol : AutoDetect + Pending Discovery Protocol : Not Applicable + + + +---------------------------------------------------------------------- +Array Information +---------------------------------------------------------------------- +Array Number 0 + Name : A + Status : Ok + Interface : SAS + Total Size : 572325 MB + Unused Size : 0 MB + Block Size : 512 Bytes + Array Utilization : 100.00% Used, 0.00% Unused + Type : Data + Transformation Status : Not Applicable + Spare Rebuild Mode : Dedicated + SSD I/O Bypass : Not Applicable + SED Encryption : Disabled +-------------------------------------------------------- + Array Logical Device Information +-------------------------------------------------------- + Logical ID : Status (RAID, Interface, SizeMB) Name +-------------------------------------------------------- + Logical 0 : Optimal (0, Data, 572293 MB) Logical Drive 1 +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 0 : Present (572325MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:1) 63M0A0BYFJPF + + +Array Number 1 + Name : B + Status : Ok + Interface : SAS + Total Size : 1144641 MB + Unused Size : 0 MB + Block Size : 512 Bytes + Array Utilization : 100.00% Used, 0.00% Unused + Type : Data + Transformation Status : Not Applicable + Spare Rebuild Mode : Dedicated + SSD I/O Bypass : Not Applicable + SED Encryption : Disabled +-------------------------------------------------------- + Array Logical Device Information +-------------------------------------------------------- + Logical ID : Status (RAID, Interface, SizeMB) Name +-------------------------------------------------------- + Logical 1 : Optimal (0, Data, 1144609 MB) Logical Drive 2 +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 1 : Present (1144641MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:2) 59M0A06CFJRG + + +Array Number 2 + Name : C + Status : Ok + Interface : SAS + Total Size : 1144641 MB + Unused Size : 0 MB + Block Size : 512 Bytes + Array Utilization : 100.00% Used, 0.00% Unused + Type : Data + Transformation Status : Not Applicable + Spare Rebuild Mode : Dedicated + SSD I/O Bypass : Not Applicable + SED Encryption : Disabled +-------------------------------------------------------- + Array Logical Device Information +-------------------------------------------------------- + Logical ID : Status (RAID, Interface, SizeMB) Name +-------------------------------------------------------- + Logical 2 : Optimal (0, Data, 1144609 MB) Logical Drive 3 +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 2 : Present (1144641MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:3) WFK076DT0000E821CET2 + + + +-------------------------------------------------------- +Logical device information +-------------------------------------------------------- +Logical Device number 0 + Logical Device name : Logical Drive 1 + Disk Name : /dev/sdd (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 : 572293 MB + Stripe-unit size : 128 KB + Full Stripe Size : 128 KB + Interface Type : Serial Attached SCSI + Device Type : Data + Boot Type : None + Heads : 255 + Sectors Per Track : 32 + Cylinders : 65535 + Caching : Enabled + Mount Points : Not Mounted + LD Acceleration Method : Controller Cache + SED Encryption : Disabled + Volume Unique Identifier : 600508B1001CB9F0FE988FC40CD17395 +-------------------------------------------------------- + Consistency Check Information +-------------------------------------------------------- + Consistency Check Status : Not Applicable + Last Consistency Check Completion Time : Not Applicable + Last Consistency Check Duration : Not Applicable +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 0 : Present (572325MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:1) 63M0A0BYFJPF + + +Logical Device number 1 + Logical Device name : Logical Drive 2 + Disk Name : /dev/sde (Disk0) (Bus: 1, Target: 0, Lun: 1) + Block Size of member drives : 512 Bytes + Array : 1 + RAID level : 0 + Status of Logical Device : Optimal + Size : 1144609 MB + Stripe-unit size : 128 KB + Full Stripe Size : 128 KB + Interface Type : Serial Attached SCSI + Device Type : Data + Boot Type : None + Heads : 255 + Sectors Per Track : 32 + Cylinders : 65535 + Caching : Enabled + Mount Points : /boot/efi 1075 MB Partition Number 1 + LD Acceleration Method : Controller Cache + SED Encryption : Disabled + Volume Unique Identifier : 600508B1001CFE283CA96614826A7F85 +-------------------------------------------------------- + Consistency Check Information +-------------------------------------------------------- + Consistency Check Status : Not Applicable + Last Consistency Check Completion Time : Not Applicable + Last Consistency Check Duration : Not Applicable +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 1 : Present (1144641MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:2) 59M0A06CFJRG + + +Logical Device number 2 + Logical Device name : Logical Drive 3 + Disk Name : /dev/sdc (Disk0) (Bus: 1, Target: 0, Lun: 2) + Block Size of member drives : 512 Bytes + Array : 2 + RAID level : 0 + Status of Logical Device : Optimal + Size : 1144609 MB + Stripe-unit size : 128 KB + Full Stripe Size : 128 KB + Interface Type : Serial Attached SCSI + Device Type : Data + Boot Type : Primary and Secondary + Heads : 255 + Sectors Per Track : 32 + Cylinders : 65535 + Caching : Enabled + Mount Points : Not Mounted + LD Acceleration Method : Controller Cache + SED Encryption : Disabled + Volume Unique Identifier : 600508B1001C6DB81E7099960E5B5796 +-------------------------------------------------------- + Consistency Check Information +-------------------------------------------------------- + Consistency Check Status : Not Applicable + Last Consistency Check Completion Time : Not Applicable + Last Consistency Check Duration : Not Applicable +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 2 : Present (1144641MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:3) WFK076DT0000E821CET2 + + + +---------------------------------------------------------------------- +Physical Device information +---------------------------------------------------------------------- + Channel #0: + Device #0 + Device is a Hard drive + State : Online + Drive has stale RIS data : False + Block Size : 512 Bytes + Physical Block Size : 512 Bytes + Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,0(0:0) + Reported Location : Backplane 0, Slot 1(Connector 0:CN2) + Array : 0 + Vendor : TOSHIBA + Model : AL15SEB060N + Firmware : 5703 + Serial number : 63M0A0BYFJPF + World-wide name : 5000039C983AD88A + Reserved Size : 32768 KB + Used Size : 572293 MB + Unused Size : 0 MB + Total Size : 572325 MB + Write Cache : Disabled (write-through) + S.M.A.R.T. : No + S.M.A.R.T. warnings : 0 + SSD : No + Boot Type : None + Rotational Speed : 10500 RPM + Current Temperature : 20 deg C + Maximum Temperature : 20 deg C + Threshold Temperature : 65 deg C + PHY Count : 2 + Drive Configuration Type : Data + Drive Exposed to OS : False + Sanitize Erase Support : True + Sanitize Lock Freeze Support : False + Sanitize Lock Anti-Freeze Support : False + Sanitize Lock Setting : None + Drive Unique ID : 5000039C983AD889 + Drive SKU Number : Not Applicable + Drive Part Number : Not Applicable + Last Failure Reason : No Failure + ---------------------------------------------------------------- + Device Phy Information + ---------------------------------------------------------------- + Phy #0 + Negotiated Physical Link Rate : 12 Gbps + Negotiated Logical Link Rate : 12 Gbps + Maximum Link Rate : 12 Gbps + Phy #1 + Negotiated Physical Link Rate : unknown + Negotiated Logical Link Rate : unknown + Maximum Link Rate : 12 Gbps + + ---------------------------------------------------------------- + Device Error Counters + ---------------------------------------------------------------- + Aborted Commands : 0 + Bad Target Errors : 0 + Ecc Recovered Read Errors : 0 + Failed Read Recovers : 0 + Failed Write Recovers : 0 + Format Errors : 0 + Hardware Errors : 0 + Hard Read Errors : 0 + Hard Write Errors : 0 + Hot Plug Count : 0 + Media Failures : 0 + Not Ready Errors : 0 + Other Time Out Errors : 0 + Predictive Failures : 0 + Retry Recovered Read Errors : 0 + Retry Recovered Write Errors : 0 + Scsi Bus Faults : 6 + Sectors Reads : 0 + Sectors Written : 0 + Service Hours : 44 + + Device #1 + Device is a Hard drive + State : Online + Drive has stale RIS data : False + Block Size : 512 Bytes + Physical Block Size : 512 Bytes + Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,1(1:0) + Reported Location : Backplane 0, Slot 2(Connector 0:CN2) + Array : 1 + Vendor : TOSHIBA + Model : AL15SEB120N + Firmware : 5701 + Serial number : 59M0A06CFJRG + World-wide name : 50000399686BA672 + Reserved Size : 32768 KB + Used Size : 1144609 MB + Unused Size : 0 MB + Total Size : 1144641 MB + Write Cache : Disabled (write-through) + S.M.A.R.T. : No + S.M.A.R.T. warnings : 0 + SSD : No + Boot Type : None + Rotational Speed : 10500 RPM + Current Temperature : 22 deg C + Maximum Temperature : 22 deg C + Threshold Temperature : 65 deg C + PHY Count : 2 + Drive Configuration Type : Data + Drive Exposed to OS : False + Sanitize Erase Support : False + Drive Unique ID : 50000399686BA671 + Drive SKU Number : Not Applicable + Drive Part Number : Not Applicable + Last Failure Reason : No Failure + ---------------------------------------------------------------- + Device Phy Information + ---------------------------------------------------------------- + Phy #0 + Negotiated Physical Link Rate : 12 Gbps + Negotiated Logical Link Rate : 12 Gbps + Maximum Link Rate : 12 Gbps + Phy #1 + Negotiated Physical Link Rate : unknown + Negotiated Logical Link Rate : unknown + Maximum Link Rate : 12 Gbps + + ---------------------------------------------------------------- + Device Error Counters + ---------------------------------------------------------------- + Aborted Commands : 0 + Bad Target Errors : 0 + Ecc Recovered Read Errors : 0 + Failed Read Recovers : 0 + Failed Write Recovers : 0 + Format Errors : 0 + Hardware Errors : 0 + Hard Read Errors : 0 + Hard Write Errors : 0 + Hot Plug Count : 0 + Media Failures : 0 + Not Ready Errors : 0 + Other Time Out Errors : 0 + Predictive Failures : 0 + Retry Recovered Read Errors : 0 + Retry Recovered Write Errors : 0 + Scsi Bus Faults : 0 + Sectors Reads : 0 + Sectors Written : 0 + Service Hours : 44 + + Device #2 + Device is a Hard drive + State : Online + Drive has stale RIS data : False + Block Size : 512 Bytes + Physical Block Size : 512 Bytes + Transfer Speed : SAS 12.0 Gb/s + Reported Channel,Device(T:L) : 0,2(2:0) + Reported Location : Backplane 0, Slot 3(Connector 0:CN2) + Array : 2 + Vendor : SEAGATE + Model : ST1200MM0009 + Firmware : CN03 + Serial number : WFK076DT0000E821CET2 + World-wide name : 5000C500A137C8C9 + Reserved Size : 32768 KB + Used Size : 1144609 MB + Unused Size : 0 MB + Total Size : 1144641 MB + Write Cache : Disabled (write-through) + S.M.A.R.T. : No + S.M.A.R.T. warnings : 0 + SSD : No + Boot Type : None + Rotational Speed : 10500 RPM + Current Temperature : 22 deg C + Maximum Temperature : 22 deg C + Threshold Temperature : 60 deg C + PHY Count : 2 + Drive Configuration Type : Data + Drive Exposed to OS : False + Sanitize Erase Support : True + Sanitize Lock Freeze Support : False + Sanitize Lock Anti-Freeze Support : False + Sanitize Lock Setting : None + Drive Unique ID : 5000C500A137C8CB + Drive SKU Number : Not Applicable + Drive Part Number : Not Applicable + Last Failure Reason : No Failure + ---------------------------------------------------------------- + Device Phy Information + ---------------------------------------------------------------- + Phy #0 + Negotiated Physical Link Rate : 12 Gbps + Negotiated Logical Link Rate : 12 Gbps + Maximum Link Rate : 12 Gbps + Phy #1 + Negotiated Physical Link Rate : unknown + Negotiated Logical Link Rate : unknown + Maximum Link Rate : 12 Gbps + + ---------------------------------------------------------------- + Device Error Counters + ---------------------------------------------------------------- + Aborted Commands : 0 + Bad Target Errors : 0 + Ecc Recovered Read Errors : 0 + Failed Read Recovers : 0 + Failed Write Recovers : 0 + Format Errors : 0 + Hardware Errors : 0 + Hard Read Errors : 0 + Hard Write Errors : 0 + Hot Plug Count : 0 + Media Failures : 0 + Not Ready Errors : 0 + Other Time Out Errors : 0 + Predictive Failures : 0 + Retry Recovered Read Errors : 0 + Retry Recovered Write Errors : 0 + Scsi Bus Faults : 0 + Sectors Reads : 0 + Sectors Written : 0 + Service Hours : 44 + + Channel #2: + Device #0 + Device is an Enclosure Services Device + Reported Channel,Device(T:L) : 2,0(0:0) + Enclosure ID : 0 + Enclosure Logical Identifier : 50000D1E01787B10 + Type : SES2 + Vendor : Cisco + Model : Virtual SGPIO + Firmware : 0124 + Status of Enclosure Services Device + Speaker status : Not Available + + Backplane: + Device #0 + Device is an UBM Controller + Backplane ID : 0 + UBM Controller ID : 0 + Type : UBM + Firmware : 0.2 + Device Code : 3792052480 + PCI Vendor ID : 0x1000 + + +---------------------------------------------------------------------- +maxCache information +---------------------------------------------------------------------- + No maxCache Array found + + +Command completed successfully. +` + +var arcConfGetConfigLD = ` +Logical Device number 0 + Logical Device name : Logical Drive 1 + Disk Name : /dev/sdd (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 : 572293 MB + Stripe-unit size : 128 KB + Full Stripe Size : 128 KB + Interface Type : Serial Attached ATA + Device Type : Data + Boot Type : None + Heads : 255 + Sectors Per Track : 32 + Cylinders : 65535 + Caching : Enabled + Mount Points : Not Mounted + LD Acceleration Method : Controller Cache + SED Encryption : Disabled + Volume Unique Identifier : 600508B1001CB9F0FE988FC40CD17395 +-------------------------------------------------------- + Consistency Check Information +-------------------------------------------------------- + Consistency Check Status : Not Applicable + Last Consistency Check Completion Time : Not Applicable + Last Consistency Check Duration : Not Applicable +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 0 : Present (572325MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:1) 63M0A0BYFJPF + + +Logical Device number 1 + Logical Device name : Logical Drive 2 + Disk Name : /dev/sde (Disk0) (Bus: 1, Target: 0, Lun: 1) + Block Size of member drives : 512 Bytes + Array : 1 + RAID level : 0 + Status of Logical Device : Optimal + Size : 1144609 MB + Stripe-unit size : 128 KB + Full Stripe Size : 128 KB + Interface Type : Serial Attached SCSI + Device Type : Data + Boot Type : None + Heads : 255 + Sectors Per Track : 32 + Cylinders : 65535 + Caching : Enabled + Mount Points : /boot/efi 1075 MB Partition Number 1 + LD Acceleration Method : Controller Cache + SED Encryption : Disabled + Volume Unique Identifier : 600508B1001CFE283CA96614826A7F85 +-------------------------------------------------------- + Consistency Check Information +-------------------------------------------------------- + Consistency Check Status : Not Applicable + Last Consistency Check Completion Time : Not Applicable + Last Consistency Check Duration : Not Applicable +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 1 : Present (1144641MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:2) 59M0A06CFJRG + + +Logical Device number 2 + Logical Device name : Logical Drive 3 + Disk Name : /dev/sdc (Disk0) (Bus: 1, Target: 0, Lun: 2) + Block Size of member drives : 512 Bytes + Array : 2 + RAID level : 0 + Status of Logical Device : Optimal + Size : 1144609 MB + Stripe-unit size : 128 KB + Full Stripe Size : 128 KB + Interface Type : Serial Attached SCSI + Device Type : Data + Boot Type : Primary and Secondary + Heads : 255 + Sectors Per Track : 32 + Cylinders : 65535 + Caching : Enabled + Mount Points : Not Mounted + LD Acceleration Method : Controller Cache + SED Encryption : Disabled + Volume Unique Identifier : 600508B1001C6DB81E7099960E5B5796 +-------------------------------------------------------- + Consistency Check Information +-------------------------------------------------------- + Consistency Check Status : Not Applicable + Last Consistency Check Completion Time : Not Applicable + Last Consistency Check Duration : Not Applicable +-------------------------------------------------------- + Array Physical Device Information +-------------------------------------------------------- + Device ID : Availability (SizeMB, Protocol, Type, Connector ID, Location) Serial Number +-------------------------------------------------------- + Device 2 : Present (1144641MB, SAS, HDD, Connector:CN2, Backplane:0, Slot:3) WFK076DT0000E821CET2 + + + +Command completed successfully. +` + +var arcList = ` +Controllers found: 3 +---------------------------------------------------------------------- +Controller information +---------------------------------------------------------------------- + Controller ID : Status, Slot, Mode, Name, SerialNumber, WWN +---------------------------------------------------------------------- + Controller 1: : Optimal, Slot 16, Mixed, Cisco 24G TriMode M1 RAID 4GB FBWC 16D UCSC-RAID-M1L16, 3137F30003A, 50000D1E01787B00 + Controller 2: : Optimal, Slot 9, Mixed, Cisco 24G TriMode M1 RAID 4GB FBWC 16D UCSC-RAID-M1L16, A312J89902X, 50000XKJLKJSJHFG + Controller 3: : Optimal, Slot 4, Mixed, Cisco 24G TriMode M1 RAID 4GB FBWC 16D UCSC-RAID-M1L16, B8812N9128Y, 50000ZKJSK8391J9 + +Command completed successfully. +` + +var arcListBad = ` +Controllers found: 3 +---------------------------------------------------------------------- +Controller information +---------------------------------------------------------------------- + Controller ID : Status, Slot, Mode, Name, SerialNumber, WWN +---------------------------------------------------------------------- + Controller 1: : Optimal, Slot 16, Mixed, Cisco 24G TriMode M1 RAID 4GB FBWC 16D UCSC-RAID-M1L16, 3137F30003A, 50000D1E01787B00 + +Command completed successfully. +` + +func TestSmartPqiList(t *testing.T) { + found, err := parseList(arcList) + if err != nil { + t.Errorf("failed to parse arcconf list command output: %s", err) + } + + if len(found) != 3 { + t.Errorf("parseList found %d expected 3", len(found)) + } + + expected := []int{1, 2, 3} + for i := range found { + if !reflect.DeepEqual(expected[i], found[i]) { + t.Errorf("entry %d: found controller ID: %d expected: %d", i, expected[i], found[i]) + } + } +} + +func TestSmartPqiListBad(t *testing.T) { + _, err := parseList(arcListBad) + if err == nil { + t.Errorf("expected error on bad input") + } + + expected := "mismatched output, found 1 controllers, expected 3" + if !strings.Contains(err.Error(), expected) { + t.Errorf("expected '%s' message, got %q", expected, err) + } +} + +func TestSmartPqiLogicaDevice(t *testing.T) { + expectedLDs := []LogicalDevice{} + expectedLDsJSON := []string{ + `{"ArrayID":0,"BlockSize":512,"Devices":null,"DiskName":"/dev/sdd","ID":0,"InterfaceType":"ATA","Name":"Logical Drive 1","RAIDLevel":"0","SizeMB":572293}`, + `{"ArrayID":1,"BlockSize":512,"Devices":null,"DiskName":"/dev/sde","ID":1,"InterfaceType":"SCSI","Name":"Logical Drive 2","RAIDLevel":"0","SizeMB":1144609}`, + `{"ArrayID":2,"BlockSize":512,"Devices":null,"DiskName":"/dev/sdc","ID":2,"InterfaceType":"SCSI","Name":"Logical Drive 3","RAIDLevel":"0","SizeMB":1144609}`, + } + + for idx, content := range expectedLDsJSON { + ld := LogicalDevice{} + if err := json.Unmarshal([]byte(content), &ld); err != nil { + t.Errorf("failed to unmarshal expected LD JSON index %d: %s", idx, err) + } + if len(ld.DiskName) == 0 { + t.Fatalf("Failed to unmarshal JSON blob correctly") + } + expectedLDs = append(expectedLDs, ld) + } + + if len(expectedLDs) != len(expectedLDsJSON) { + t.Errorf("failed to marshall expected number of LogicalDevice, found %d, expected %d", len(expectedLDs), len(expectedLDsJSON)) + } + + found, err := parseLogicalDevices(arcConfGetConfigLD) + if err != nil { + t.Errorf("failed to parse arcconf getconfig 1 ld: %s", err) + } + + for i := range found { + if !reflect.DeepEqual(expectedLDs[i], found[i]) { + t.Errorf("entry %d logical device differed:\n found: %#v\n expct: %#v ", i, found[i], expectedLDs[i]) + } + } +} + +func TestSmartPqiLogicaDeviceBadInput(t *testing.T) { + var input = `Logical Device number 0` + found, err := parseLogicalDevices(input) + if err == nil { + t.Errorf("did not return error with bad input") + } + if len(found) != 0 { + t.Errorf("expected 0, found %d", len(found)) + } + expectedErr := "expected exactly 2 lines of data, found" + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("expected %q, got %q", expectedErr, err) + } +} + +func TestSmartPqiLogicaDeviceBadDeviceNumber(t *testing.T) { + var input = ` +Logical Device number #! + Logical Device name : Logical Drive 1 + Disk Name : /dev/sdd (Disk0) (Bus: 1, Target: 0, Lun: 0) +` + found, err := parseLogicalDevices(input) + if err == nil { + t.Errorf("did not return error with bad output") + } + if len(found) != 0 { + t.Errorf("expected 0, found %d", len(found)) + } + expectedErr := "error while parsing integer from" + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("expected %q message, got %q", expectedErr, err) + } +} + +func TestSmartPqiLogicaDeviceNumberMultidigit(t *testing.T) { + var input = ` +Logical Device number 127 + Logical Device name : Logical Drive 127 + Disk Name : /dev/sdd (Disk0) (Bus: 1, Target: 0, Lun: 0) + Array : 127 +` + found, err := parseLogicalDevices(input) + if err != nil { + t.Errorf("expected err to be nil, got %s", err) + } + if len(found) != 1 { + t.Errorf("expected 1, found %d", len(found)) + } + if found[0].ArrayID != 127 { + t.Errorf("expected logical device with ArrayID = 127, got %d", found[0].ArrayID) + } +} + +func TestSmartPqiLogicaDeviceBadAtoiTest(t *testing.T) { + var inputArray = ` +Logical Device number 0 + Logical Device name : Logical Drive 1 + Disk Name : /dev/sdd (Disk0) (Bus: 1, Target: 0, Lun: 0) + Array : err + Block Size of member drives : 512 Bytes + Size : 1144641 MB +` + var inputBsize = ` +Logical Device number 0 + Logical Device name : Logical Drive 1 + Disk Name : /dev/sdd (Disk0) (Bus: 1, Target: 0, Lun: 0) + Array : 1 + Block Size of member drives : zero Bytes + Size : 1144641 MB +` + var inputSize = ` +Logical Device number 0 + Logical Device name : Logical Drive 1 + Disk Name : /dev/sdd (Disk0) (Bus: 1, Target: 0, Lun: 0) + Array : 1 + Block Size of member drives : 512 Bytes + Size : xxxxx MB +` + + testCases := []struct { + input string + expectedErr string + }{ + {inputArray, "failed to parse ArrayID from token"}, + {inputBsize, "failed to parse BlockSize from token"}, + {inputSize, "failed to parse Size from token"}, + } + + for idx, testCase := range testCases { + found, err := parseLogicalDevices(testCase.input) + if err == nil { + t.Errorf("did not return error with bad input index %d", idx) + } + if len(found) != 0 { + t.Errorf("expected 0, found %d index %d", len(found), idx) + } + if !strings.Contains(err.Error(), testCase.expectedErr) { + t.Errorf("expected '%s' message, got %q index %d", testCase.expectedErr, err, idx) + } + } +} + +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)"}`, + } + + for idx, content := range expectedPDsJSON { + pd := PhysicalDevice{} + if err := json.Unmarshal([]byte(content), &pd); err != nil { + t.Errorf("failed to unmarshal expected PD JSON index %d: %s", idx, err) + } + if len(pd.SerialNumber) == 0 { + t.Fatalf("Failed to unmarshal JSON blob correctly") + } + expectedPDs = append(expectedPDs, pd) + } + + if len(expectedPDs) != len(expectedPDsJSON) { + t.Errorf("failed to marshall expected number of PhysicalDevices, found %d, expected %d", len(expectedPDs), len(expectedPDsJSON)) + } + + _, found, err := parseGetConf(arcConfGetConfig) + if err != nil { + t.Errorf("failed to parse arcconf getconfig 1: %s", err) + } + + for i := range found { + if !reflect.DeepEqual(expectedPDs[i], found[i]) { + t.Errorf("entry %d physical device differed:\n found: %#v\n expct: %#v ", i, found[i], expectedPDs[i]) + } + } +} + +func TestSmartPqiPhysicalDeviceBadFormats(t *testing.T) { + var inputShort = ` + Device #0 +` + var inputDevID = ` + Device #err + Device is a Hard drive + State : Online + Drive has stale RIS data : False + Block Size : 512 Bytes +` + var inputBsize = ` + Device #0 + State : Online + Device is a Hard drive + Drive has stale RIS data : False + Block Size : XXX Bytes +` + var inputPhyBsize = ` + Device #0 + Physical Block Size : abc Bytes +` + var inputTotalSize = ` + Device #0 + Total Size : XXX MB +` + + testCases := []struct { + input string + expectedErr string + }{ + {inputShort, "expected more than 2 lines of data"}, + {inputDevID, "error finding start of PhysicalDevice in data"}, + {inputBsize, "failed to parse Block Size from token"}, + {inputPhyBsize, "failed to parse Physical Block Size from token"}, + {inputTotalSize, "failed to parse Total Size from token"}, + } + + for idx, testCase := range testCases { + found, err := parsePhysicalDevices(testCase.input) + if err == nil { + t.Errorf("did not return error with bad input index %d", idx) + } + if len(found) != 0 { + t.Errorf("expected 0, found %d index %d", len(found), idx) + } + if !strings.Contains(err.Error(), testCase.expectedErr) { + t.Errorf("expected '%s' message, got %q index %d", testCase.expectedErr, err, idx) + } + } +} + +func TestSmartPqiInterface(t *testing.T) { + arc := ArcConf() + if arc == nil { + t.Errorf("expected ArcConf pointer, found nil") + } +} + +func TestSmartPqiNewController(t *testing.T) { + cID := 1 + ctrl, err := newController(cID, arcConfGetConfig) + if err != nil { + 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}}}` + expCtrl := Controller{} + if err := json.Unmarshal([]byte(expectedJSON), &expCtrl); err != nil { + t.Errorf("failed to unmarshal expected Controller JSON: %s", err) + } + + if !reflect.DeepEqual(expCtrl, ctrl) { + t.Errorf("controller differed:\n found: %#v\n expct: %#v ", ctrl, expCtrl) + } +} + +func TestSmartPqiNewControllerBadInput(t *testing.T) { + var inputEmpty = `` + var inputEmptyLines = ` + + +` + testCases := []struct { + input string + expectedErr string + }{ + {inputEmpty, "failed to parse arcconf getconfig output: cannot parse an empty string"}, + {inputEmptyLines, "failed to parse arcconf getconfig output: expected more than 3 lines of data in input"}, + } + + cID := 1 + for idx, testCase := range testCases { + _, err := newController(cID, testCase.input) + if err == nil { + t.Errorf("did not return error with bad input index %d", idx) + continue + } + + if !strings.Contains(err.Error(), testCase.expectedErr) { + t.Errorf("expected '%s' message, got %q index %d", testCase.expectedErr, err, idx) + } + } +} diff --git a/smartpqi/linux.go b/smartpqi/linux.go new file mode 100644 index 0000000..bb20b6d --- /dev/null +++ b/smartpqi/linux.go @@ -0,0 +1,3 @@ +package smartpqi + +const SysfsPCIDriversPath = "/sys/bus/pci/drivers/smartpqi" diff --git a/smartpqi/smartpqi.go b/smartpqi/smartpqi.go new file mode 100644 index 0000000..a4b0b08 --- /dev/null +++ b/smartpqi/smartpqi.go @@ -0,0 +1,139 @@ +package smartpqi + +import ( + "encoding/json" + "errors" + "strings" + + "machinerun.io/disko" +) + +type Controller struct { + ID int + PhysicalDrives DriveSet + LogicalDrives LogicalDriveSet +} + +type PhysicalDevice struct { + ArrayID int `json:"ArrayID"` + Availability string `json:"Availability"` + BlockSize int `json:"BlockSize"` + Channel int `json:"Channel"` + ID int `json:"ID"` + Firmware string `json:"Firmware"` + Model string `json:"Model"` + PhysicalBlockSize int `json:"PhysicalBlockSize"` + Protocol string `json:"Protocol"` + SerialNumber string `json:"SerialNumber"` + SizeMB int `json:"SizeMB"` + Type MediaType `json:"Type"` + Vendor string `json:"Vendor"` + WriteCache string `json:"WriteCache"` +} + +type LogicalDevice struct { + ArrayID int `json:"ArrayID"` + BlockSize int `json:"BlockSize"` + Caching string `json:"Caching"` + Devices []*PhysicalDevice + DiskName string `json:"DiskName"` + ID int `json:"ID"` + InterfaceType string `json:"InterfaceType"` + Name string `json:"Name"` + RAIDLevel string `json:"RAIDLevel"` + SizeMB int `json:"SizeMB"` +} + +// IsSSD - is this logical device composed of all SSD +func (ld *LogicalDevice) IsSSD() bool { + if len(ld.Devices) == 0 { + return false + } + + for _, pDev := range ld.Devices { + if pDev.Type != SSD { + return false + } + } + + return true +} + +type DriveSet map[int]*PhysicalDevice + +type LogicalDriveSet map[int]*LogicalDevice + +// MediaType +type MediaType int + +const ( + // UnknownMedia - indicates an unknown media + UnknownMedia MediaType = iota + + // HDD - Spinning hard disk. + HDD + + // SSD - Solid State Disk + SSD + + // NVME - Non Volatile Memory Express + NVME +) + +func (t MediaType) String() string { + return []string{"UNKNOWN", "HDD", "SSD", "NVME"}[t] +} + +func GetMediaType(mediaType string) MediaType { + switch strings.ToUpper(mediaType) { + case "HDD": + return HDD + case "SSD": + return SSD + case "NVME": + return NVME + default: + return UnknownMedia + } +} + +// MarshalJSON for string output rather than int +func (t MediaType) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + +func (t *MediaType) UnmarshalJSON(data []byte) error { + var mt string + if err := json.Unmarshal(data, &mt); err != nil { + return err + } + *t = GetMediaType(mt) + return nil +} + +// SmartPqi - basic interface +type SmartPqi interface { + // List - Return list of Controller IDs + List() ([]int, error) + + // Query - Query the controller provided + Query(int) (Controller, error) + + // GetDiskType - Determine the disk type if controller owns disk + GetDiskType(string) (disko.DiskType, error) + + // DriverSysfsPath - Return the sysfs path to the linux driver for this controller + DriverSysfsPath() string + + // IsSysPathRAID - Check if sysfs path is a device on the controller + IsSysPathRAID(string) bool +} + +// ErrNoController - Error reported by Query if no controller is found. +var ErrNoController = errors.New("smartpqi Controller not found") + +// ErrUnsupported - Error reported by Query if controller is not supported. +var ErrUnsupported = errors.New("smartpqi Controller unsupported") + +// ErrNoArcconf - Error reported by Query if no arcconf binary in PATH +var ErrNoArcconf = errors.New("no 'arcconf' command in PATH")