diff --git a/pkg/core/go.sum b/pkg/core/go.sum deleted file mode 100644 index d1de9f4..0000000 --- a/pkg/core/go.sum +++ /dev/null @@ -1 +0,0 @@ -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/pkg/domain/entities/logicalvolume/methods.go b/pkg/domain/entities/logicalvolume/methods.go index 2e92f5c..524567c 100644 --- a/pkg/domain/entities/logicalvolume/methods.go +++ b/pkg/domain/entities/logicalvolume/methods.go @@ -122,7 +122,7 @@ func ValidateRAIDCreation( } // Check if there are unavailable drives - unavailableDrives := unavailablesDrives(pds) + unavailableDrives := unavailableDrives(pds) // If there are unavailable drives, return an error if len(unavailableDrives) > 0 { @@ -148,8 +148,8 @@ func ValidateRAIDCreation( return nil } -// unavailablesDrives returns the IDs of the unavailable physical drives. -func unavailablesDrives(pds []*physicaldrive.PhysicalDrive) []string { +// unavailableDrives returns the IDs of the unavailable physical drives. +func unavailableDrives(pds []*physicaldrive.PhysicalDrive) []string { var unavailableDrives []string for _, pd := range pds { @@ -203,14 +203,17 @@ func findMostFrequentSize(pds []*physicaldrive.PhysicalDrive) uint64 { // RAIDLevelMap maps the RAID level string to the RAID level type. func RAIDLevelMap(str string) RAIDLevel { + // Remove the "RAID" prefix from the string if it exists + raidLevelString := strings.TrimPrefix(str, "RAID") + // raidLevelMap maps the RAID level string to the RAID level type. raidLevelMap := map[string]RAIDLevel{ - "RAID0": RAIDLevel0, - "RAID1": RAIDLevel1, - "RAID10": RAIDLevel10, + "0": RAIDLevel0, + "1": RAIDLevel1, + "10": RAIDLevel10, } - if raidLevel, ok := raidLevelMap[strings.ToUpper(str)]; ok { + if raidLevel, ok := raidLevelMap[raidLevelString]; ok { return raidLevel } diff --git a/pkg/domain/entities/physicaldrive/enums.go b/pkg/domain/entities/physicaldrive/enums.go index 79884d1..ec62126 100644 --- a/pkg/domain/entities/physicaldrive/enums.go +++ b/pkg/domain/entities/physicaldrive/enums.go @@ -10,7 +10,9 @@ const ( DiskTypeHDD DiskTypeSSD DiskTypeNVMe +) +const ( PDStatusUnknown PDStatus = iota PDStatusUsed PDStatusUnassignedGood diff --git a/pkg/domain/entities/physicaldrive/types.go b/pkg/domain/entities/physicaldrive/types.go index 6605072..3446662 100644 --- a/pkg/domain/entities/physicaldrive/types.go +++ b/pkg/domain/entities/physicaldrive/types.go @@ -1,4 +1,4 @@ -//nolint:lll // Structures with tags are too long for you, lll. +//nolint:lll,cyclop,gocognit // Structures with tags are too long for you, lll. package physicaldrive import ( @@ -74,6 +74,37 @@ func (s *Slot) String() string { return str } +func (s *Slot) Format() string { + if s == nil { + return nilSlot + } + + // Handle empty cases for all fields + if s.Port == "" && s.Enclosure == "" && s.Bay == "" { + return emptySlot + } + + result := s.Port + + if s.Enclosure != "" { + if result != "" { + result += ":" + } + + result += s.Enclosure + } + + if s.Bay != "" { + if result != "" { + result += ":" + } + + result += s.Bay + } + + return result +} + // Available checks if the PhysicalDrive Status is PDStatusUnassignedGood. func (pd *PhysicalDrive) IsAvailable() bool { return pd.Status == PDStatusUnassignedGood diff --git a/pkg/domain/ports/raidcontroller.go b/pkg/domain/ports/raidcontroller.go index 95297c8..8079ad1 100644 --- a/pkg/domain/ports/raidcontroller.go +++ b/pkg/domain/ports/raidcontroller.go @@ -1,11 +1,17 @@ package ports import ( + "github.com/pkg/errors" + "github.com/scality/raidmgmt/pkg/domain/entities/logicalvolume" "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" ) +const functionNotSupportedByImplementation = "function not supported by implementation" + +var ErrFunctionNotSupportedByImplementation = errors.New(functionNotSupportedByImplementation) + type ( ControllersGetter interface { // Controllers returns a list of RAID controllers diff --git a/pkg/implementation/blinker/ssacli.go b/pkg/implementation/blinker/ssacli.go new file mode 100644 index 0000000..92a4654 --- /dev/null +++ b/pkg/implementation/blinker/ssacli.go @@ -0,0 +1,62 @@ +package blinker + +import ( + "strconv" + + "github.com/pkg/errors" + + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/commandrunner" +) + +type SSACLI struct { + commandrunner.CommandRunner +} + +var _ ports.Blinker = &SSACLI{} + +func NewSSACLI() *SSACLI { + return &SSACLI{} +} + +// StartBlink starts blinking a physical drive. +func (s *SSACLI) StartBlink(metadata *physicaldrive.Metadata) error { + err := s.blink(metadata, "on") + if err != nil { + return errors.Wrap(err, "failed to start blinking physical drive") + } + + return nil +} + +// StopBlink stops blinking a physical drive. +func (s *SSACLI) StopBlink(metadata *physicaldrive.Metadata) error { + err := s.blink(metadata, "off") + if err != nil { + return errors.Wrap(err, "failed to stop blinking physical drive") + } + + return nil +} + +// blink makes a physical drive blink. +func (s *SSACLI) blink(metadata *physicaldrive.Metadata, action string) error { + slot := metadata.Slot.Format() + + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.CtrlMetadata.ID), + "physicaldrive", + slot, + "modify", + "led=" + action, + } + + _, err := s.CommandRunner.Run(args) + if err != nil { + return errors.Wrapf(err, "failed to blink physical drive %s", slot) + } + + return nil +} diff --git a/pkg/implementation/controllergetter/controller/show/all.txt b/pkg/implementation/controllergetter/controller/show/all.txt new file mode 100644 index 0000000..0b2c2de --- /dev/null +++ b/pkg/implementation/controllergetter/controller/show/all.txt @@ -0,0 +1,3 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) (sn: PWXLA0CRHF10FM) + diff --git a/pkg/implementation/controllergetter/controller/show/all_detail.txt b/pkg/implementation/controllergetter/controller/show/all_detail.txt new file mode 100644 index 0000000..de53c10 --- /dev/null +++ b/pkg/implementation/controllergetter/controller/show/all_detail.txt @@ -0,0 +1,89 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + Bus Interface: PCI + Slot: 0 + Serial Number: PWXLA0CRHF10FM + RAID 6 Status: Enabled + Controller Status: OK + Hardware Revision: A + Firmware Version: 5.00 + Firmware Supports Online Firmware Activation: False + Rebuild Priority: High + Expand Priority: Medium + Surface Scan Delay: 3 secs + Surface Scan Mode: Idle + Parallel Surface Scan Supported: Yes + Current Parallel Surface Scan Count: 1 + Max Parallel Surface Scan Count: 16 + Queue Depth: Automatic + Monitor and Performance Delay: 60 min + Elevator Sort: Enabled + Degraded Performance Optimization: Disabled + Inconsistency Repair Policy: Disabled + Write Cache Bypass Threshold Size: 1040 KiB + Wait for Cache Room: Disabled + Surface Analysis Inconsistency Notification: Disabled + Post Prompt Timeout: 15 secs + Cache Board Present: True + Cache Status: OK + Cache Ratio: 10% Read / 90% Write + Configured Drive Write Cache Policy: Disable + Unconfigured Drive Write Cache Policy: Default + Total Cache Size: 4.0 + Total Cache Memory Available: 3.8 + Battery Backed Cache Size: 3.8 + No-Battery Write Cache: Disabled + SSD Caching RAID5 WriteBack Enabled: True + SSD Caching Version: 2 + Cache Backup Power Source: Batteries + Battery/Capacitor Count: 1 + Battery/Capacitor Status: OK + SATA NCQ Supported: True + Spare Activation Mode: Activate on physical drive failure (default) + Spare Spindown Policy Supported: False + Controller Temperature (C): 64 + Capacitor Temperature (C): 50 + Number of Ports: 4 Internal only + Encryption: Not Set + Express Local Encryption: False + SED Based Encryption Supported: False + Driver Name: smartpqi + Driver Version: Linux 2.1.24-046 + WWN Port: 51402EC0167C85E0 + PCI Address (Domain:Bus:Device.Function): 0000:5C:00.0 + Negotiated PCIe Data Rate: PCIe 3.0 x8 (7880 MB/s) + Controller Mode: Mixed + Port Max Phy Rate Limiting Supported: False + Latency Scheduler Setting: Disabled + Current Power Mode: MaxPerformance + Survival Mode: Enabled + Host Serial Number: 2M21210201 + Sanitize Erase Supported: True + Sanitize Lock: None + Sensor ID: 0 + Location: Capacitor + Current Value (C): 50 + Max Value Since Power On: 52 + Sensor ID: 1 + Location: ASIC + Current Value (C): 64 + Max Value Since Power On: 67 + Sensor ID: 2 + Location: Unknown + Current Value (C): 50 + Max Value Since Power On: 52 + Primary Boot Volume: None + Secondary Boot Volume: None + SPDM Supports Get Slot Certificate Chain: no + SPDM Supports Get Controller Info : no + SPDM Supports Get Slot Info : no + SPDM Supports Set Import Certificate : no + SPDM Supports Set Invalidate Slot : no + Surface Scan Completion Supported: False + Persistent Event Log Policy Change Supported: False + UEFI Health Reporting Mode Supported: False + Firmware Supports NVMe Log Pages: False + Firmware Supports NAND Field: False + Firmware Supports NOR Field: False + + diff --git a/pkg/implementation/controllergetter/controller/show/config.txt b/pkg/implementation/controllergetter/controller/show/config.txt new file mode 100644 index 0000000..7b0623f --- /dev/null +++ b/pkg/implementation/controllergetter/controller/show/config.txt @@ -0,0 +1,112 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) (sn: PWXLA0CRHF10FM) + + + + Internal Drive Cage at Port 1I, Box 1 (Index 0), OK + + + + Internal Drive Cage at Port 2I, Box 2 (Index 1), OK + + + + Internal Drive Cage at Port 3I, Box 3 (Index 2), OK + + + + Internal Drive Cage at Port 4I, Box 6 (Index 3), OK + + + Port Name: 1I (Mixed) + + Port Name: 2I (Mixed) + + Port Name: 3I (Mixed) + + Port Name: 4I (Mixed) + + Array A (Solid State SAS, Unused Space: 0 MB) + + logicaldrive 1 (745.18 GB, RAID 1, OK) + + physicaldrive 4I:6:1 (port 4I:box 6:bay 1, SAS SSD, 800 GB, OK) + physicaldrive 4I:6:2 (port 4I:box 6:bay 2, SAS SSD, 800 GB, OK) + + + Array B (SAS, Unused Space: 0 MB) + + logicaldrive 2 (5.46 TB, RAID 0, OK) + + physicaldrive 1I:1:3 (port 1I:box 1:bay 3, SAS HDD, 6 TB, OK) + + + Array C (SAS, Unused Space: 0 MB) + + logicaldrive 3 (5.46 TB, RAID 0, OK) + + physicaldrive 1I:1:4 (port 1I:box 1:bay 4, SAS HDD, 6 TB, OK) + + + Array D (SAS, Unused Space: 0 MB) + + logicaldrive 4 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:1 (port 2I:box 2:bay 1, SAS HDD, 6 TB, OK) + + + Array E (SAS, Unused Space: 0 MB) + + logicaldrive 5 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:2 (port 2I:box 2:bay 2, SAS HDD, 6 TB, OK) + + + Array F (SAS, Unused Space: 0 MB) + + logicaldrive 6 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:3 (port 2I:box 2:bay 3, SAS HDD, 6 TB, OK) + + + Array G (SAS, Unused Space: 0 MB) + + logicaldrive 7 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:4 (port 2I:box 2:bay 4, SAS HDD, 6 TB, OK) + + + Array H (SAS, Unused Space: 0 MB) + + logicaldrive 8 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:1 (port 3I:box 3:bay 1, SAS HDD, 6 TB, OK) + + + Array I (SAS, Unused Space: 0 MB) + + logicaldrive 9 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:2 (port 3I:box 3:bay 2, SAS HDD, 6 TB, OK) + + + Array J (SAS, Unused Space: 0 MB) + + logicaldrive 10 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:3 (port 3I:box 3:bay 3, SAS HDD, 6 TB, OK) + + + Array K (SAS, Unused Space: 0 MB) + + logicaldrive 11 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:4 (port 3I:box 3:bay 4, SAS HDD, 6 TB, OK) + + Unassigned + + physicaldrive 1I:1:1 (port 1I:box 1:bay 1, SAS HDD, 2 TB, OK) + physicaldrive 1I:1:2 (port 1I:box 1:bay 2, SAS HDD, 2 TB, OK) + + SEP (Vendor ID HPE, Model Smart Adapter) 379 (WWID: 51402EC0167C85F0) + diff --git a/pkg/implementation/controllergetter/controller/show/slot_0.txt b/pkg/implementation/controllergetter/controller/show/slot_0.txt new file mode 100644 index 0000000..03fceda --- /dev/null +++ b/pkg/implementation/controllergetter/controller/show/slot_0.txt @@ -0,0 +1,89 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + Bus Interface: PCI + Slot: 0 + Serial Number: PWXLA0CRHF10FM + RAID 6 Status: Enabled + Controller Status: OK + Hardware Revision: A + Firmware Version: 5.00 + Firmware Supports Online Firmware Activation: False + Rebuild Priority: High + Expand Priority: Medium + Surface Scan Delay: 3 secs + Surface Scan Mode: Idle + Parallel Surface Scan Supported: Yes + Current Parallel Surface Scan Count: 1 + Max Parallel Surface Scan Count: 16 + Queue Depth: Automatic + Monitor and Performance Delay: 60 min + Elevator Sort: Enabled + Degraded Performance Optimization: Disabled + Inconsistency Repair Policy: Disabled + Write Cache Bypass Threshold Size: 1040 KiB + Wait for Cache Room: Disabled + Surface Analysis Inconsistency Notification: Disabled + Post Prompt Timeout: 15 secs + Cache Board Present: True + Cache Status: OK + Cache Ratio: 10% Read / 90% Write + Configured Drive Write Cache Policy: Disable + Unconfigured Drive Write Cache Policy: Default + Total Cache Size: 4.0 + Total Cache Memory Available: 3.8 + Battery Backed Cache Size: 3.8 + No-Battery Write Cache: Disabled + SSD Caching RAID5 WriteBack Enabled: True + SSD Caching Version: 2 + Cache Backup Power Source: Batteries + Battery/Capacitor Count: 1 + Battery/Capacitor Status: OK + SATA NCQ Supported: True + Spare Activation Mode: Activate on physical drive failure (default) + Spare Spindown Policy Supported: False + Controller Temperature (C): 60 + Capacitor Temperature (C): 48 + Number of Ports: 4 Internal only + Encryption: Not Set + Express Local Encryption: False + SED Based Encryption Supported: False + Driver Name: smartpqi + Driver Version: Linux 2.1.24-046 + WWN Port: 51402EC0167C85E0 + PCI Address (Domain:Bus:Device.Function): 0000:5C:00.0 + Negotiated PCIe Data Rate: PCIe 3.0 x8 (7880 MB/s) + Controller Mode: Mixed + Port Max Phy Rate Limiting Supported: False + Latency Scheduler Setting: Disabled + Current Power Mode: MaxPerformance + Survival Mode: Enabled + Host Serial Number: 2M21210201 + Sanitize Erase Supported: True + Sanitize Lock: None + Sensor ID: 0 + Location: Capacitor + Current Value (C): 48 + Max Value Since Power On: 52 + Sensor ID: 1 + Location: ASIC + Current Value (C): 60 + Max Value Since Power On: 66 + Sensor ID: 2 + Location: Unknown + Current Value (C): 46 + Max Value Since Power On: 51 + Primary Boot Volume: None + Secondary Boot Volume: None + SPDM Supports Get Slot Certificate Chain: no + SPDM Supports Get Controller Info : no + SPDM Supports Get Slot Info : no + SPDM Supports Set Import Certificate : no + SPDM Supports Set Invalidate Slot : no + Surface Scan Completion Supported: False + Persistent Event Log Policy Change Supported: False + UEFI Health Reporting Mode Supported: False + Firmware Supports NVMe Log Pages: False + Firmware Supports NAND Field: False + Firmware Supports NOR Field: False + + diff --git a/pkg/implementation/controllergetter/ssacli.go b/pkg/implementation/controllergetter/ssacli.go new file mode 100644 index 0000000..b24c623 --- /dev/null +++ b/pkg/implementation/controllergetter/ssacli.go @@ -0,0 +1,141 @@ +package controllergetter + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/commandrunner" + "github.com/scality/raidmgmt/pkg/utils" +) + +const ( + // Capture leading whitespace. + sscaliLeadingWhitespaceRegexpPattern = `^(\s*)` + ssacliNameRegexpPattern = `HPE Smart Array (.*?) in Slot \d+` + ssacliKeyValueParts = 2 +) + +type SSACLI struct { + commandrunner.CommandRunner +} + +var ( + _ ports.ControllersGetter = &SSACLI{} + + sscaliLeadingWhitespaceRegexp = regexp.MustCompile(sscaliLeadingWhitespaceRegexpPattern) + nameRegexp = regexp.MustCompile(ssacliNameRegexpPattern) +) + +func NewSSACLI(commandRunner commandrunner.CommandRunner) *SSACLI { + return &SSACLI{ + CommandRunner: commandRunner, + } +} + +// Controllers returns a list of RAID controllers. +func (s *SSACLI) Controllers() ([]*raidcontroller.RAIDController, error) { + output, err := s.CommandRunner.Run([]string{ + "controller", + "all", + "show", + "detail", + }) + if err != nil { + return nil, errors.Wrap(err, "failed to show all controllers details") + } + + controllers, err := parseControllers(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse controllers details") + } + + return controllers, nil +} + +// Controller returns a RAID controller for a given metadata. +func (s *SSACLI) Controller(metadata *raidcontroller.Metadata) ( + *raidcontroller.RAIDController, + error, +) { + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.ID), + "show", + "detail", + } + + output, err := s.CommandRunner.Run(args) + if err != nil { + return nil, errors.Wrapf(err, "failed to show details for controller %d", metadata.ID) + } + + controller, err := parseController(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse controller") + } + + return controller, nil +} + +func parseControllers(output []byte) ([]*raidcontroller.RAIDController, error) { + blocks := utils.SplitOutput(sscaliLeadingWhitespaceRegexp, output) + + controllers := make([]*raidcontroller.RAIDController, 0, len(blocks)) + + for _, block := range blocks { + controller, err := parseController(block) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse controller: %s", block) + } + + controllers = append(controllers, controller) + } + + return controllers, nil +} + +// parseController parses a controller block and returns a RAIDController entity. +func parseController(block []byte) (*raidcontroller.RAIDController, error) { + controller := &raidcontroller.RAIDController{ + Metadata: &raidcontroller.Metadata{}, + } + + for line := range strings.SplitSeq(string(block), "\n") { + if err := parseControllerLine(controller, line); err != nil { + return nil, errors.Wrapf(err, "failed to parse controller line: %s", line) + } + } + + return controller, nil +} + +// parseControllerLine parses a line of a controller block and updates the RAIDController entity. +func parseControllerLine(controller *raidcontroller.RAIDController, line string) error { + if nameRegexp.FindStringSubmatch(line) != nil { + controller.Name = nameRegexp.FindStringSubmatch(line)[1] + + return nil + } + + key, value := utils.ParseLineDetail(line) + + switch key { + case "Serial Number": + controller.Serial = value + + case "Slot": + idInt, err := strconv.Atoi(value) + if err != nil { + return errors.Wrap(err, "failed to convert controller slot ID to int") + } + + controller.ID = idInt + } + + return nil +} diff --git a/pkg/implementation/controllergetter/ssacli_test.go b/pkg/implementation/controllergetter/ssacli_test.go new file mode 100644 index 0000000..84b4d85 --- /dev/null +++ b/pkg/implementation/controllergetter/ssacli_test.go @@ -0,0 +1,121 @@ +package controllergetter + +import ( + "os" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" +) + +type MockCommandRunner struct { + mock.Mock +} + +func (m *MockCommandRunner) Run(args []string) ([]byte, error) { + arguments := m.Called(args) + + return arguments.Get(0).([]byte), arguments.Error(1) +} + +var testDataPath = "./" + +func mockOutput(filename string) []byte { + output, err := os.ReadFile(testDataPath + filename + ".txt") + if err != nil { + panic(err) + } + + return output +} + +// TestControllers tests the Controllers method. +func TestControllers(t *testing.T) { + mockRunner := new(MockCommandRunner) + + s := &SSACLI{ + CommandRunner: mockRunner, + } + + tests := []struct { + name string + mocking []byte + expectedError bool + }{ + { + name: "nominal case", + mocking: mockOutput("controller/show/all_detail"), + expectedError: false, + }, + // TODO add more test cases + } + + for _, tt := range tests { + mockRunner.On("Run", []string{"controller", "all", "show", "detail"}).Return(tt.mocking, nil) + + controllers, err := s.Controllers() + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, controllers) + } else { + assert.NoError(t, err) + assert.NotEmpty(t, controllers) + + for _, controller := range controllers { + assert.NotEmpty(t, controller.Name) + assert.NotEmpty(t, controller.Serial) + } + + for _, controller := range controllers { + t.Logf("Controller %d: %+v", controller.ID, controller) + } + } + } +} + +func TestController(t *testing.T) { + mockRunner := new(MockCommandRunner) + + s := &SSACLI{ + CommandRunner: mockRunner, + } + + tests := []struct { + name string + mocking []byte + id int + expectedError bool + }{ + { + name: "nominal case", + mocking: mockOutput("controller/show/slot_0"), + id: 0, + expectedError: false, + }, + // TODO add more test cases + } + + for _, tt := range tests { + mockRunner.On("Run", []string{"controller", "slot=" + strconv.Itoa(tt.id), "show", "detail"}).Return(tt.mocking, nil) + + metadata := &raidcontroller.Metadata{ + ID: tt.id, + } + + controller, err := s.Controller(metadata) + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, controller) + } else { + assert.NoError(t, err) + assert.NotNil(t, controller) + + assert.Equal(t, metadata.ID, controller.ID) + assert.Equal(t, "P816i-a SR Gen10", controller.Name) + assert.Equal(t, "PWXLA0CRHF10FM", controller.Serial) + } + } +} diff --git a/pkg/implementation/hardwareraidcontroller/megaraid/mocks/Runner.go b/pkg/implementation/hardwareraidcontroller/megaraid/mocks/Runner.go index 65909b7..b829fdd 100644 --- a/pkg/implementation/hardwareraidcontroller/megaraid/mocks/Runner.go +++ b/pkg/implementation/hardwareraidcontroller/megaraid/mocks/Runner.go @@ -3,8 +3,9 @@ package mocks import ( - megaraid "github.com/scality/raidmgmt/pkg/implementation/hardwareraidcontroller/megaraid" - mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/mock" + + "github.com/scality/raidmgmt/pkg/implementation/hardwareraidcontroller/megaraid" ) // Runner is an autogenerated mock type for the Runner type @@ -47,8 +48,7 @@ func (_m *Runner) Run(args []string) (*megaraid.CmdOutput, error) { func NewRunner(t interface { mock.TestingT Cleanup(func()) -}, -) *Runner { +}) *Runner { mock := &Runner{} mock.Mock.Test(t) diff --git a/pkg/implementation/hardwareraidcontroller/smartarray.go b/pkg/implementation/hardwareraidcontroller/smartarray.go new file mode 100644 index 0000000..af259b8 --- /dev/null +++ b/pkg/implementation/hardwareraidcontroller/smartarray.go @@ -0,0 +1,57 @@ +package hardwareraidcontroller + +import ( + "github.com/pkg/errors" + + "github.com/scality/raidmgmt/pkg/domain/entities/logicalvolume" + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + "github.com/scality/raidmgmt/pkg/domain/ports" +) + +type SmartArray struct { + ports.ControllersGetter + ports.PhysicalDrivesGetter + ports.LogicalVolumesGetter + ports.LogicalVolumesManager + ports.Blinker +} + +var _ ports.HardwareRAIDController = &SmartArray{} + +//nolint:revive // This wraps interfaces together. +func NewSmartArray( + controllersGetter ports.ControllersGetter, + physicalDrivesGetter ports.PhysicalDrivesGetter, + logicalVolumesGetter ports.LogicalVolumesGetter, + logicalVolumesManager ports.LogicalVolumesManager, + blinker ports.Blinker, +) *SmartArray { + return &SmartArray{ + ControllersGetter: controllersGetter, + PhysicalDrivesGetter: physicalDrivesGetter, + LogicalVolumesGetter: logicalVolumesGetter, + LogicalVolumesManager: logicalVolumesManager, + Blinker: blinker, + } +} + +func (*SmartArray) EnableJBOD(_ *physicaldrive.Metadata) error { + return errors.Wrap( + ports.ErrFunctionNotSupportedByImplementation, + "cannot enable JBOD on SmartArray", + ) +} + +func (*SmartArray) DisableJBOD(_ *physicaldrive.Metadata) error { + return errors.Wrap( + ports.ErrFunctionNotSupportedByImplementation, + "cant disable JBOD on SmartArray", + ) +} + +func (*SmartArray) SetLVCacheOptions(*logicalvolume.Metadata, *logicalvolume.CacheOptions) error { + return errors.Wrap( + ports.ErrFunctionNotSupportedByImplementation, + "cannot set cache options on SmartArray", + ) +} diff --git a/pkg/implementation/hardwareraidcontroller/smartarray/ssacli.go b/pkg/implementation/hardwareraidcontroller/smartarray/ssacli.go deleted file mode 100644 index 930b28b..0000000 --- a/pkg/implementation/hardwareraidcontroller/smartarray/ssacli.go +++ /dev/null @@ -1 +0,0 @@ -package smartarray diff --git a/pkg/implementation/logicalvolumegetter/controller/show/config.txt b/pkg/implementation/logicalvolumegetter/controller/show/config.txt new file mode 100644 index 0000000..7b0623f --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/controller/show/config.txt @@ -0,0 +1,112 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) (sn: PWXLA0CRHF10FM) + + + + Internal Drive Cage at Port 1I, Box 1 (Index 0), OK + + + + Internal Drive Cage at Port 2I, Box 2 (Index 1), OK + + + + Internal Drive Cage at Port 3I, Box 3 (Index 2), OK + + + + Internal Drive Cage at Port 4I, Box 6 (Index 3), OK + + + Port Name: 1I (Mixed) + + Port Name: 2I (Mixed) + + Port Name: 3I (Mixed) + + Port Name: 4I (Mixed) + + Array A (Solid State SAS, Unused Space: 0 MB) + + logicaldrive 1 (745.18 GB, RAID 1, OK) + + physicaldrive 4I:6:1 (port 4I:box 6:bay 1, SAS SSD, 800 GB, OK) + physicaldrive 4I:6:2 (port 4I:box 6:bay 2, SAS SSD, 800 GB, OK) + + + Array B (SAS, Unused Space: 0 MB) + + logicaldrive 2 (5.46 TB, RAID 0, OK) + + physicaldrive 1I:1:3 (port 1I:box 1:bay 3, SAS HDD, 6 TB, OK) + + + Array C (SAS, Unused Space: 0 MB) + + logicaldrive 3 (5.46 TB, RAID 0, OK) + + physicaldrive 1I:1:4 (port 1I:box 1:bay 4, SAS HDD, 6 TB, OK) + + + Array D (SAS, Unused Space: 0 MB) + + logicaldrive 4 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:1 (port 2I:box 2:bay 1, SAS HDD, 6 TB, OK) + + + Array E (SAS, Unused Space: 0 MB) + + logicaldrive 5 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:2 (port 2I:box 2:bay 2, SAS HDD, 6 TB, OK) + + + Array F (SAS, Unused Space: 0 MB) + + logicaldrive 6 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:3 (port 2I:box 2:bay 3, SAS HDD, 6 TB, OK) + + + Array G (SAS, Unused Space: 0 MB) + + logicaldrive 7 (5.46 TB, RAID 0, OK) + + physicaldrive 2I:2:4 (port 2I:box 2:bay 4, SAS HDD, 6 TB, OK) + + + Array H (SAS, Unused Space: 0 MB) + + logicaldrive 8 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:1 (port 3I:box 3:bay 1, SAS HDD, 6 TB, OK) + + + Array I (SAS, Unused Space: 0 MB) + + logicaldrive 9 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:2 (port 3I:box 3:bay 2, SAS HDD, 6 TB, OK) + + + Array J (SAS, Unused Space: 0 MB) + + logicaldrive 10 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:3 (port 3I:box 3:bay 3, SAS HDD, 6 TB, OK) + + + Array K (SAS, Unused Space: 0 MB) + + logicaldrive 11 (5.46 TB, RAID 0, OK) + + physicaldrive 3I:3:4 (port 3I:box 3:bay 4, SAS HDD, 6 TB, OK) + + Unassigned + + physicaldrive 1I:1:1 (port 1I:box 1:bay 1, SAS HDD, 2 TB, OK) + physicaldrive 1I:1:2 (port 1I:box 1:bay 2, SAS HDD, 2 TB, OK) + + SEP (Vendor ID HPE, Model Smart Adapter) 379 (WWID: 51402EC0167C85F0) + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/1.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/1.txt new file mode 100644 index 0000000..dff4eac --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/1.txt @@ -0,0 +1,33 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array A + + Logical Drive: 1 + Size: 745.18 GB + Fault Tolerance: 1 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + Unrecoverable Media Errors: None + MultiDomain Status: OK + Caching: Disabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001CF3D41DB2683BA2B29D47 + Disk Name: /dev/sdc + Mount Points: 1024 MiB Partition 2, 600 MiB Partition 1 /boot, /boot/efi + Disk Partition Information + Partition 2: Basic, 1024 MiB, /boot + Partition 1: Basic, 600 MiB, /boot/efi + Logical Drive Label: OS Drive + Mirror Group 1: + physicaldrive 4I:6:1 (port 4I:box 6:bay 1, SAS SSD, 800 GB, OK) + Mirror Group 2: + physicaldrive 4I:6:2 (port 4I:box 6:bay 2, SAS SSD, 800 GB, OK) + Drive Type: Data + LD Acceleration Method: Smart Path + + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/2.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/2.txt new file mode 100644 index 0000000..d10232f --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/2.txt @@ -0,0 +1,25 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array B + + Logical Drive: 2 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C7FD57A17DFABD4A1E6D9 + Disk Name: /dev/sdd + Mount Points: None + Logical Drive Label: Data 01 + Drive Type: Data + LD Acceleration Method: Controller Cache + + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/all.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/all.txt new file mode 100644 index 0000000..4771963 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/detail/all.txt @@ -0,0 +1,253 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array A + + Logical Drive: 1 + Size: 745.18 GB + Fault Tolerance: 1 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + Unrecoverable Media Errors: None + MultiDomain Status: OK + Caching: Disabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001CF3D41DB2683BA2B29D47 + Disk Name: /dev/sdc + Mount Points: 1024 MiB Partition 2, 600 MiB Partition 1 /boot, /boot/efi + Disk Partition Information + Partition 2: Basic, 1024 MiB, /boot + Partition 1: Basic, 600 MiB, /boot/efi + Logical Drive Label: OS Drive + Mirror Group 1: + physicaldrive 4I:6:1 (port 4I:box 6:bay 1, SAS SSD, 800 GB, OK) + Mirror Group 2: + physicaldrive 4I:6:2 (port 4I:box 6:bay 2, SAS SSD, 800 GB, OK) + Drive Type: Data + LD Acceleration Method: Smart Path + + + Array B + + Logical Drive: 2 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C7FD57A17DFABD4A1E6D9 + Disk Name: /dev/sdd + Mount Points: None + Logical Drive Label: Data 01 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array C + + Logical Drive: 3 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C3B89E5AA34EE69C875EF + Disk Name: /dev/sde + Mount Points: None + Logical Drive Label: Data 02 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array D + + Logical Drive: 4 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001CF16E1ABE7F6C898859C8 + Disk Name: /dev/sdf + Mount Points: None + Logical Drive Label: Data 03 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array E + + Logical Drive: 5 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C813ED7FBDF3E45DC17C7 + Disk Name: /dev/sdg + Mount Points: None + Logical Drive Label: Data 04 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array F + + Logical Drive: 6 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001CB13B0302F1C621256AD8 + Disk Name: /dev/sdh + Mount Points: None + Logical Drive Label: Data 05 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array G + + Logical Drive: 7 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C6BE4ED7555256C8A2E1D + Disk Name: /dev/sdi + Mount Points: None + Logical Drive Label: Data 06 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array H + + Logical Drive: 8 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001CE106EAF4D6B7BA94E4BA + Disk Name: /dev/sdj + Mount Points: None + Logical Drive Label: Data 07 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array I + + Logical Drive: 9 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C9B1F1877A9E3DF7CDF6C + Disk Name: /dev/sdk + Mount Points: None + Logical Drive Label: Data 08 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array J + + Logical Drive: 10 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001CA7668EC1E6339D3DB914 + Disk Name: /dev/sdl + Mount Points: None + Logical Drive Label: Data 09 + Drive Type: Data + LD Acceleration Method: Controller Cache + + + Array K + + Logical Drive: 11 + Size: 5.46 TB + Fault Tolerance: 0 + Heads: 255 + Sectors Per Track: 32 + Cylinders: 65535 + Strip Size: 256 KB + Full Stripe Size: 256 KB + Status: OK + MultiDomain Status: OK + Caching: Enabled + Last Surface Scan Completed: False + Unique Identifier: 600508B1001C87D2241C10D9F3BA0121 + Disk Name: /dev/sdm + Mount Points: None + Logical Drive Label: Data 10 + Drive Type: Data + LD Acceleration Method: Controller Cache + + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/1.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/1.txt new file mode 100644 index 0000000..669b43b --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/1.txt @@ -0,0 +1,3 @@ + + logicaldrive 1 (745.18 GB, RAID 1): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/10.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/10.txt new file mode 100644 index 0000000..e0372ba --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/10.txt @@ -0,0 +1,3 @@ + + logicaldrive 10 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/11.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/11.txt new file mode 100644 index 0000000..2043283 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/11.txt @@ -0,0 +1,3 @@ + + logicaldrive 11 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/2.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/2.txt new file mode 100644 index 0000000..dba1917 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/2.txt @@ -0,0 +1,3 @@ + + logicaldrive 2 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/3.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/3.txt new file mode 100644 index 0000000..4a05727 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/3.txt @@ -0,0 +1,3 @@ + + logicaldrive 3 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/4.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/4.txt new file mode 100644 index 0000000..56d432e --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/4.txt @@ -0,0 +1,3 @@ + + logicaldrive 4 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/5.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/5.txt new file mode 100644 index 0000000..cf73f01 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/5.txt @@ -0,0 +1,3 @@ + + logicaldrive 5 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/6.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/6.txt new file mode 100644 index 0000000..785aed7 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/6.txt @@ -0,0 +1,3 @@ + + logicaldrive 6 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/7.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/7.txt new file mode 100644 index 0000000..03d00c6 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/7.txt @@ -0,0 +1,3 @@ + + logicaldrive 7 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/8.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/8.txt new file mode 100644 index 0000000..02351ae --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/8.txt @@ -0,0 +1,3 @@ + + logicaldrive 8 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/9.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/9.txt new file mode 100644 index 0000000..838fbe2 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/9.txt @@ -0,0 +1,3 @@ + + logicaldrive 9 (5.46 TB, RAID 0): OK + diff --git a/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/all.txt b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/all.txt new file mode 100644 index 0000000..647ce06 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/logicalvolumes/show/status/all.txt @@ -0,0 +1,12 @@ + + logicaldrive 1 (745.18 GB, RAID 1): OK + logicaldrive 2 (5.46 TB, RAID 0): OK + logicaldrive 3 (5.46 TB, RAID 0): OK + logicaldrive 4 (5.46 TB, RAID 0): OK + logicaldrive 5 (5.46 TB, RAID 0): OK + logicaldrive 6 (5.46 TB, RAID 0): OK + logicaldrive 7 (5.46 TB, RAID 0): OK + logicaldrive 8 (5.46 TB, RAID 0): OK + logicaldrive 9 (5.46 TB, RAID 0): OK + logicaldrive 10 (5.46 TB, RAID 0): OK + logicaldrive 11 (5.46 TB, RAID 0): OK diff --git a/pkg/implementation/logicalvolumegetter/mdadm.go b/pkg/implementation/logicalvolumegetter/mdadm.go index daa38dd..3b94e4b 100644 --- a/pkg/implementation/logicalvolumegetter/mdadm.go +++ b/pkg/implementation/logicalvolumegetter/mdadm.go @@ -234,7 +234,7 @@ func ParseMDADMExportOutput(output []byte) ([]*MDADMExportDetails, error) { for _, line := range strings.Split(string(block), "\n") { switch { case strings.HasPrefix(line, "MD_LEVEL="): - raidLevel := strings.TrimPrefix(line, "MD_LEVEL=") + raidLevel := strings.TrimPrefix(line, "MD_LEVEL=raid") currentDetails.RaidLevel = logicalvolume.RAIDLevelMap(raidLevel) case strings.HasPrefix(line, "MD_DEVICES="): diff --git a/pkg/implementation/logicalvolumegetter/ssacli.go b/pkg/implementation/logicalvolumegetter/ssacli.go new file mode 100644 index 0000000..9f69b39 --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/ssacli.go @@ -0,0 +1,281 @@ +package logicalvolumegetter + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/scality/raidmgmt/pkg/domain/entities/logicalvolume" + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/commandrunner" + "github.com/scality/raidmgmt/pkg/utils" +) + +const ( + ssacliLogicalVolumeRegexpPattern = `\s*Logical Drive:\s+\d+` + ssacliLogicalVolumeIDStatusRegexpPattern = `logicaldrive\s+(\d+)` + ssacliRAIDLevelRegexpPattern = `RAID\s+(\d+)` + ssacliArrayOrUnassignedRegexpPattern = `(Array\s+[A-Z]+\s+\(.*\)|Unassigned)` + + ssacliPhysicalDriveConfigRegexpPattern = `physicaldrive\s+.+?\s+\(port\s+(.+?):box\s+(.+?):bay\s+(.+?),.*\)` // nolint: lll // This is a regexp + + ssacliMinStringMatches = 2 +) + +type SSACLI struct { + ssacli commandrunner.CommandRunner + lsblk commandrunner.LSBLK +} + +var ( + _ ports.LogicalVolumesGetter = &SSACLI{} + + ssacliLogicalVolumeRegexp = regexp.MustCompile(ssacliLogicalVolumeRegexpPattern) + ssacliLogicalVolumeIDStatusRegexp = regexp.MustCompile(ssacliLogicalVolumeIDStatusRegexpPattern) + ssacliRAIDLevelRegexp = regexp.MustCompile(ssacliRAIDLevelRegexpPattern) + ssacliArrayOrUnassignedRegexp = regexp.MustCompile(ssacliArrayOrUnassignedRegexpPattern) + ssacliPhysicalDriveConfigRegexp = regexp.MustCompile(ssacliPhysicalDriveConfigRegexpPattern) +) + +func NewSSACLI( + ssacli commandrunner.CommandRunner, + lsblk commandrunner.LSBLK, +) *SSACLI { + return &SSACLI{ + ssacli: ssacli, + lsblk: lsblk, + } +} + +// LogicalVolumes returns all logical volumes for a given controller. +func (s *SSACLI) LogicalVolumes(metadata *raidcontroller.Metadata) ( + []*logicalvolume.LogicalVolume, + error, +) { + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.ID), + "logicaldrive", + "all", + "show", + "detail", + } + + output, err := s.ssacli.Run(args) + if err != nil { + return nil, errors.Wrap(err, "failed to show all logical drives details") + } + + logicalVolumes, err := parseLogicalVolumes(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse logical drives details") + } + + // Get the controller config to get the physical drives metadata and RAID level + args = []string{ + "controller", + "slot=" + strconv.Itoa(metadata.ID), + "show", + "config", + } + + output, err = s.ssacli.Run(args) + if err != nil { + return nil, errors.Wrap(err, "failed to show controller config") + } + + for _, lv := range logicalVolumes { + // Set the controller metadata + lv.CtrlMetadata = metadata + + // Extract the RAID level and physical drives metadata + raidLevel, pdsMetadata := extractInfoFromConfig(lv, output) + lv.RAIDLevel = raidLevel + lv.PDrivesMetadata = pdsMetadata + } + + return logicalVolumes, nil +} + +// LogicalVolume returns a logical volume for a given metadata. +func (s *SSACLI) LogicalVolume(metadata *logicalvolume.Metadata) ( + *logicalvolume.LogicalVolume, + error, +) { + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.CtrlMetadata.ID), + "logicaldrive", + metadata.ID, + "show", + "detail", + } + + output, err := s.ssacli.Run(args) + if err != nil { + return nil, errors.Wrapf(err, "failed to show details for logical drive %s", metadata.ID) + } + + logicalVolume, err := parseLogicalVolume(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse logical drive") + } + + logicalVolume.Metadata = metadata + + // Get the controller config to get the physical drives metadata and RAID level + args = []string{ + "controller", + "slot=" + strconv.Itoa(metadata.CtrlMetadata.ID), + "show", + "config", + } + + output, err = s.ssacli.Run(args) + if err != nil { + return nil, errors.Wrap(err, "failed to show controller config") + } + + // Extract the RAID level and physical drives metadata + raidLevel, pdsMetadata := extractInfoFromConfig(logicalVolume, output) + logicalVolume.RAIDLevel = raidLevel + logicalVolume.PDrivesMetadata = pdsMetadata + + return logicalVolume, nil +} + +func parseLogicalVolumes(output []byte) ( + []*logicalvolume.LogicalVolume, + error, +) { + blocks := utils.SplitOutput(ssacliLogicalVolumeRegexp, output) + + logicalVolumes := make([]*logicalvolume.LogicalVolume, 0, len(blocks)) + + for _, block := range blocks { + logicalVolume, err := parseLogicalVolume(block) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse logical volume: %s", block) + } + + logicalVolumes = append(logicalVolumes, logicalVolume) + } + + return logicalVolumes, nil +} + +func parseLogicalVolume(block []byte) ( + *logicalvolume.LogicalVolume, + error, +) { + // Create the LogicalVolume entity + logicalVolume := &logicalvolume.LogicalVolume{ + Metadata: &logicalvolume.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{}, + ID: "", + }, + } + + for line := range strings.SplitSeq(string(block), "\n") { + if err := parseLVLine(logicalVolume, line); err != nil { + return nil, errors.Wrap(err, "failed to parse line") + } + } + + return logicalVolume, nil +} + +// parseLVLine parses a line of the logical volume output and updates the logical volume entity. +func parseLVLine(logicalVolume *logicalvolume.LogicalVolume, line string) error { + key, value := utils.ParseLineDetail(line) + + switch key { + case "Logical Drive": + logicalVolume.ID = value + case "Status": + mapStatus := map[string]logicalvolume.LVStatus{ + "OK": logicalvolume.LVStatusOptimal, + "Failed": logicalvolume.LVStatusFailed, + // TODO check real values + } + + status, ok := mapStatus[value] + if !ok { + return errors.Errorf("invalid status: %s", value) + } + + logicalVolume.Status = status + case "Disk Name": + logicalVolume.DevicePath = value + // TODO miss permanent path + } + + return nil +} + +// extractInfoFromConfig extracts the RAID level and the physical drives metadata +// from the config show output +// it is necessary to keep it as is. +// +//nolint:gocognit // This function may seem complex but due to the "continue" statements +func extractInfoFromConfig( + logicalVolume *logicalvolume.LogicalVolume, + output []byte, +) (logicalvolume.RAIDLevel, []*physicaldrive.Metadata) { + blocks := utils.SplitOutput(ssacliArrayOrUnassignedRegexp, output) + + // Get the physical drives metadata + pDrivesMetadata := make([]*physicaldrive.Metadata, 0, len(blocks)) + raidLevel := logicalVolume.RAIDLevel + + for _, block := range blocks { + // Parse the block only if the id of logical volume match + idMatch := ssacliLogicalVolumeIDStatusRegexp.FindStringSubmatch(string(block)) + if len(idMatch) < ssacliMinStringMatches || idMatch[1] != logicalVolume.ID { + continue + } + + for line := range strings.SplitSeq(string(block), "\n") { + // Get the RAID level + if raidLevel.String() == "Unknown" { + raidLevel = extractRAIDLevel(line) + } + + matches := ssacliPhysicalDriveConfigRegexp.FindStringSubmatch(line) + //nolint:mnd // The matches required here are 4 + if len(matches) < 4 { + continue + } + + // Create the PhysicalDrive metadata + pDriveMetadata := &physicaldrive.Metadata{ + CtrlMetadata: logicalVolume.CtrlMetadata, + Slot: &physicaldrive.Slot{ + Port: matches[1], + Enclosure: matches[2], + Bay: matches[3], + }, + } + + pDrivesMetadata = append(pDrivesMetadata, pDriveMetadata) + } + } + + return raidLevel, pDrivesMetadata +} + +func extractRAIDLevel(line string) logicalvolume.RAIDLevel { + var raidLevel logicalvolume.RAIDLevel + + raidMatch := ssacliRAIDLevelRegexp.FindStringSubmatch(line) + + if len(raidMatch) > 1 { + raidLevel = logicalvolume.RAIDLevelMap(raidMatch[1]) + } + + return raidLevel +} diff --git a/pkg/implementation/logicalvolumegetter/ssacli_test.go b/pkg/implementation/logicalvolumegetter/ssacli_test.go new file mode 100644 index 0000000..cfd91dd --- /dev/null +++ b/pkg/implementation/logicalvolumegetter/ssacli_test.go @@ -0,0 +1,188 @@ +package logicalvolumegetter + +import ( + "os" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/scality/raidmgmt/pkg/domain/entities/logicalvolume" + "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" +) + +type MockCommandRunner struct { + mock.Mock +} + +var testDataPath = "./" + +func (m *MockCommandRunner) Run(args []string) ([]byte, error) { + arguments := m.Called(args) + + return arguments.Get(0).([]byte), arguments.Error(1) +} + +func TestLogicalVolumes(t *testing.T) { + mockRunner := new(MockCommandRunner) + + s := &SSACLI{ + ssacli: mockRunner, + } + + mapMockingStatusNominal := map[string][]byte{ + "1": mockOutput("logicalvolumes/show/status/1"), + "2": mockOutput("logicalvolumes/show/status/2"), + "3": mockOutput("logicalvolumes/show/status/3"), + "4": mockOutput("logicalvolumes/show/status/4"), + "5": mockOutput("logicalvolumes/show/status/5"), + "6": mockOutput("logicalvolumes/show/status/6"), + "7": mockOutput("logicalvolumes/show/status/7"), + "8": mockOutput("logicalvolumes/show/status/8"), + "9": mockOutput("logicalvolumes/show/status/9"), + "10": mockOutput("logicalvolumes/show/status/10"), + "11": mockOutput("logicalvolumes/show/status/11"), + } + + tests := []struct { + name string + mockingDetail []byte + mockingStatus map[string][]byte + id int + expectedError bool + }{ + { + name: "nominal case", + mockingDetail: mockOutput("logicalvolumes/show/detail/all"), + mockingStatus: mapMockingStatusNominal, + id: 0, + expectedError: false, + }, + // TODO add more test cases + } + + for _, tt := range tests { + mockRunner.On("Run", []string{ + "controller", + "slot=" + strconv.Itoa(tt.id), + "logicaldrive", + "all", + "show", + "detail", + }).Return(tt.mockingDetail, nil) + + mockRunner.On("Run", []string{ + "controller", + "slot=" + strconv.Itoa(tt.id), + "show", + "config", + }).Return(mockOutput("controller/show/config"), nil) + + metadata := &raidcontroller.Metadata{ + ID: tt.id, + } + + logicalVolumes, err := s.LogicalVolumes(metadata) + + seen := make(map[string]bool) + + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, logicalVolumes) + } else { + assert.NoError(t, err) + assert.NotEmpty(t, logicalVolumes) + assert.Len(t, logicalVolumes, 11) + assert.Equal(t, logicalvolume.RAIDLevel1, logicalVolumes[0].RAIDLevel) + assert.Equal(t, logicalvolume.RAIDLevel0, logicalVolumes[1].RAIDLevel) + + for _, lv := range logicalVolumes { + if seen[lv.ID] { + t.Errorf("Duplicate logical drive: %s", lv.ID) + } else { + seen[lv.ID] = true + } + + t.Logf("Logical Drive %s: %+v", lv.ID, lv) + } + } + } +} + +func TestLogicalVolume(t *testing.T) { + mockRunner := new(MockCommandRunner) + + s := &SSACLI{ + ssacli: mockRunner, + } + + tests := []struct { + name string + mockingDetail []byte + metadata *logicalvolume.Metadata + expected *logicalvolume.LogicalVolume + expectedError bool + }{ + { + name: "nominal case", + mockingDetail: mockOutput("logicalvolumes/show/detail/1"), + metadata: &logicalvolume.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{ + ID: 0, + }, + ID: "1", + }, + expected: &logicalvolume.LogicalVolume{ + Metadata: &logicalvolume.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{ + ID: 0, + }, + ID: "1", + }, + }, + expectedError: false, + }, + // TODO add more test cases + } + + for _, tt := range tests { + mockRunner.On("Run", []string{ + "controller", + "slot=" + strconv.Itoa(tt.metadata.CtrlMetadata.ID), + "show", + "config", + }).Return(mockOutput("controller/show/config"), nil) + + mockRunner.On("Run", []string{ + "controller", + "slot=" + strconv.Itoa(tt.metadata.CtrlMetadata.ID), + "logicaldrive", + tt.metadata.ID, + "show", + "detail", + }).Return(tt.mockingDetail, nil) + + logicalVolume, err := s.LogicalVolume(tt.metadata) + + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, logicalVolume) + } else { + assert.NoError(t, err) + assert.NotNil(t, logicalVolume) + + assert.Equal(t, tt.metadata.ID, logicalVolume.ID) + assert.Equal(t, tt.metadata.CtrlMetadata.ID, logicalVolume.CtrlMetadata.ID) + } + } +} + +func mockOutput(filename string) []byte { + output, err := os.ReadFile(testDataPath + filename + ".txt") + if err != nil { + panic(err) + } + + return output +} diff --git a/pkg/implementation/logicalvolumemanager/ssacli.go b/pkg/implementation/logicalvolumemanager/ssacli.go new file mode 100644 index 0000000..c649234 --- /dev/null +++ b/pkg/implementation/logicalvolumemanager/ssacli.go @@ -0,0 +1,321 @@ +//nolint:cyclop // Juuuuust above complexity limit +package logicalvolumemanager + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/scality/raidmgmt/pkg/domain/entities/logicalvolume" + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/commandrunner" + "github.com/scality/raidmgmt/pkg/utils" +) + +const ( + ssacliArrayOrUnassignedRegexpPattern = `(Array\s+[A-Z]+\s+\(.*\)|Unassigned)` + ssacliArrayIDRegexpPattern = `Array\s+(\w+)` + + ssacliMinMatches = 2 +) + +type SSACLI struct { + ports.PhysicalDrivesGetter + ports.LogicalVolumesGetter + commandrunner.CommandRunner +} + +var ( + _ ports.LogicalVolumesManager = &SSACLI{} + + ssacliArrayOrUnassignedRegexp = regexp.MustCompile(ssacliArrayOrUnassignedRegexpPattern) + ssacliArrayIDRegexp = regexp.MustCompile(ssacliArrayIDRegexpPattern) +) + +func NewSSACLI( + commandRunner commandrunner.CommandRunner, + physicalDrivesGetter ports.PhysicalDrivesGetter, + logicalVolumesGetter ports.LogicalVolumesGetter, +) *SSACLI { + return &SSACLI{ + CommandRunner: commandRunner, + PhysicalDrivesGetter: physicalDrivesGetter, + LogicalVolumesGetter: logicalVolumesGetter, + } +} + +// CreateLV creates a logical volume from a request. +// +//nolint:funlen // This function is long. +func (s *SSACLI) CreateLV(request *logicalvolume.Request) (*logicalvolume.LogicalVolume, error) { + physicalDrivesToUse := make([]*physicaldrive.PhysicalDrive, 0, len(request.PDrivesMetadata)) + + for _, pdMetadata := range request.PDrivesMetadata { + pd, err := s.PhysicalDrive(pdMetadata) + if err != nil { + return nil, errors.Wrapf(err, "failed to get physical drive %s", + pdMetadata.Slot.Format()) + } + + physicalDrivesToUse = append(physicalDrivesToUse, pd) + } + + // Validate the RAID creation + err := logicalvolume.ValidateRAIDCreation(physicalDrivesToUse, request.RAIDLevel) + if err != nil { + return nil, errors.Wrap(err, "failed to validate RAID creation") + } + + // Format the physical drives + drives := formatDrives(request.PDrivesMetadata) + + // Convert the RAID level to SSA CLI format + raidLevel := string(request.RAIDLevel) + if request.RAIDLevel == logicalvolume.RAIDLevel10 { + raidLevel = "1+0" + } + + // Create the logical volume + args := []string{ + "controller", + "slot=" + strconv.Itoa(request.CtrlMetadata.ID), + "create", + "type=ld", + "drives=" + drives, + "raid=" + raidLevel, + "forced", // To bypass the warning and confirmation prompt + } + + _, err = s.CommandRunner.Run(args) + if err != nil { + return nil, errors.Wrap(err, "failed to run create logical drive command") + } + + // Find the new logical drive using the controller config + // Get the controller config to get the physical drives metadata and RAID level + args = []string{ + "controller", + "slot=" + strconv.Itoa(request.CtrlMetadata.ID), + "show", + "config", + } + + output, err := s.CommandRunner.Run(args) + if err != nil { + return nil, errors.Wrap(err, "failed to show controller config") + } + + newLogicalDrive, err := s.findNewLogicalDrive(request, output) + if err != nil { + return nil, errors.Wrap(err, "failed to find the new logical drive") + } + + return newLogicalDrive, nil +} + +// DeleteLV deletes a logical volume. +func (s *SSACLI) DeleteLV(metadata *logicalvolume.Metadata) error { + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.CtrlMetadata.ID), + "logicaldrive", + metadata.ID, + "delete", + "forced", // To bypass the warning message + } + + _, err := s.CommandRunner.Run(args) + if err != nil { + return errors.Wrapf(err, "failed to delete logical drive %s", metadata.ID) + } + + return nil +} + +// AddPDsToLV adds a physical drive to a logical volume. +func (s *SSACLI) AddPDsToLV( + lvMetadata *logicalvolume.Metadata, + pdsMetadata ...*physicaldrive.Metadata, +) error { + arrayID, err := s.getArrayID(lvMetadata) + if err != nil { + return errors.Wrapf(err, "failed to get array ID for logical drive %s", lvMetadata.ID) + } + + err = s.migrateArray(arrayID, lvMetadata, pdsMetadata, "add") + if err != nil { + return errors.Wrapf(err, "failed to expand array %s (logical drive %s) with physical drives", + arrayID, lvMetadata.ID) + } + + return nil +} + +// DeletePDsFromLV deletes a physical drive from a logical volume. +func (s *SSACLI) DeletePDsFromLV( + lvMetadata *logicalvolume.Metadata, + pdsMetadata ...*physicaldrive.Metadata, +) error { + arrayID, err := s.getArrayID(lvMetadata) + if err != nil { + return errors.Wrapf( + err, + "failed to get array ID for logical drive %s", + lvMetadata.ID, + ) + } + + err = s.migrateArray(arrayID, lvMetadata, pdsMetadata, "remove") + if err != nil { + return errors.Wrapf( + err, + "failed to shrink array %s (logical drive %s) with physical drives", + arrayID, lvMetadata.ID, + ) + } + + return nil +} + +// findNewLogicalDrive finds the new logical drive created by the controller. +// It returns the new logical drive and an error if any. +func (s *SSACLI) findNewLogicalDrive( + request *logicalvolume.Request, + output []byte, +) ( + *logicalvolume.LogicalVolume, error, +) { + id, err := getLogicalDriveID(request, output) + if err != nil { + return nil, errors.Wrap(err, "failed to find logical drive ID") + } + + // Get the logical drive details + metadata := &logicalvolume.Metadata{ + CtrlMetadata: request.CtrlMetadata, + ID: id, + } + + newLV, err := s.LogicalVolume(metadata) + if err != nil { + return nil, errors.Wrapf(err, "failed to get new logical drive %s", id) + } + + return newLV, nil +} + +// getArrayID gets the array ID of the logical volume. +func (s *SSACLI) getArrayID(metadata *logicalvolume.Metadata) (string, error) { + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.CtrlMetadata.ID), + "logicaldrive", + metadata.ID, + "show", + "detail", + } + + output, err := s.CommandRunner.Run(args) + if err != nil { + return "", errors.Wrapf(err, "failed to show details for logical drive %s", metadata.ID) + } + + matches := ssacliArrayIDRegexp.FindStringSubmatch(string(output)) + if len(matches) < ssacliMinMatches { + return "", errors.New("failed to parse array ID") + } + + return matches[1], nil +} + +// migrateArray migrates the physical drives to the logical volume. +// +// action can be "add" or "remove". +func (s *SSACLI) migrateArray( + arrayID string, + lvMetadata *logicalvolume.Metadata, + pdsMetadata []*physicaldrive.Metadata, + action string, +) error { + args := []string{ + "controller", + "slot=" + strconv.Itoa(lvMetadata.CtrlMetadata.ID), + "array", + arrayID, + action, + "drives=" + formatDrives(pdsMetadata), + "forced", // To bypass the warning + } + + _, err := s.CommandRunner.Run(args) + if err != nil { + return errors.Wrapf(err, "failed to %s drives to array %s", action, arrayID) + } + + return nil +} + +// formatDrives formats the physical drives to a string. +// It returns a string with the physical drives formatted as "slot1,slot2,slot3". +func formatDrives(pdsMetadata []*physicaldrive.Metadata) string { + var formattedDrives string + + if len(pdsMetadata) == 0 { + return "" + } + + formattedDrives = pdsMetadata[0].Slot.Format() + + for _, drive := range pdsMetadata[1:] { + formattedDrives += "," + drive.Slot.Format() + } + + return formattedDrives +} + +// getLogicalDriveID finds the logical drive ID that contains one of the physical drives. +// It returns the logical drive ID and an error if any. +// nolint: gocognit // This function is not too complex. +func getLogicalDriveID( + request *logicalvolume.Request, + output []byte, +) (string, error) { + blocks := utils.SplitOutput(ssacliArrayOrUnassignedRegexp, output) + + var logicalDriveID string + + for _, block := range blocks { + logicalDriveID = "" + + for line := range strings.SplitSeq(string(block), "\n") { + if strings.HasPrefix(strings.TrimSpace(line), "logicaldrive") { + // Extract the logical drive ID + parts := strings.Fields(line) + if len(parts) > 1 { + logicalDriveID = parts[1] + } + } else if strings.Contains(line, request.PDrivesMetadata[0].Slot.Format()) { + // Check if line contains the physical drive slot + // If the logical drive ID is empty, return it + // If the logical drive ID is not empty, return an error + if logicalDriveID == "" { + // Found the physical drive in the logical drive, return the logical drive ID + return logicalDriveID, nil + } + + return "", errors.Errorf( + "physical drive %s found in multiple logical drives", + request.PDrivesMetadata[0].Slot.Format(), + ) + } + } + } + + return "", errors.Errorf( + "physical drive %s not found in any logical drive", + request.PDrivesMetadata[0].Slot.Format(), + ) +} diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:1_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:1_detail.txt new file mode 100644 index 0000000..4a6941a --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:1_detail.txt @@ -0,0 +1,39 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Unassigned + + physicaldrive 1I:1:1 + Port: 1I + Box: 1 + Bay: 1 + Status: OK + Drive Type: Unassigned Drive + Interface Type: SAS + Size: 2 TB + Drive exposed to OS: True + Logical/Physical Block Size: 512/512 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: WS1061J3 + WWID: 5000C500CBD6D241 + Model: HP MB002000JWWQA + Current Temperature (C): 29 + Maximum Temperature (C): 43 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Disk Name: /dev/sda + Mount Points: None + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 3 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CBD6D243 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:2_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:2_detail.txt new file mode 100644 index 0000000..7fc77cd --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:2_detail.txt @@ -0,0 +1,39 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Unassigned + + physicaldrive 1I:1:2 + Port: 1I + Box: 1 + Bay: 2 + Status: OK + Drive Type: Unassigned Drive + Interface Type: SAS + Size: 2 TB + Drive exposed to OS: True + Logical/Physical Block Size: 512/512 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: WS106260 + WWID: 5000C500CBD6D175 + Model: HP MB002000JWWQA + Current Temperature (C): 30 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Disk Name: /dev/sdb + Mount Points: None + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 3 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CBD6D177 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:3_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:3_detail.txt new file mode 100644 index 0000000..bb85d0f --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:3_detail.txt @@ -0,0 +1,34 @@ +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array B + + physicaldrive 1I:1:3 + Port: 1I + Box: 1 + Bay: 3 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBBY88 + WWID: 5000C500CB7F22AD + Model: HP MB6000JVYZD + Current Temperature (C): 34 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB7F22AF + Multi-Actuator Drive: False \ No newline at end of file diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:4_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:4_detail.txt new file mode 100644 index 0000000..50dcd02 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/1I:1:4_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array C + + physicaldrive 1I:1:4 + Port: 1I + Box: 1 + Bay: 4 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC3RL + WWID: 5000C500CB800651 + Model: HP MB6000JVYZD + Current Temperature (C): 35 + Maximum Temperature (C): 46 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB800653 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:1_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:1_detail.txt new file mode 100644 index 0000000..a98dd93 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:1_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array D + + physicaldrive 2I:2:1 + Port: 2I + Box: 2 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC9G0 + WWID: 5000C500CB8131FD + Model: HP MB6000JVYZD + Current Temperature (C): 32 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB8131FF + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:2_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:2_detail.txt new file mode 100644 index 0000000..492de1e --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:2_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array E + + physicaldrive 2I:2:2 + Port: 2I + Box: 2 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBCAF0 + WWID: 5000C500CB80B755 + Model: HP MB6000JVYZD + Current Temperature (C): 33 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB80B757 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:3_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:3_detail.txt new file mode 100644 index 0000000..45ceeb8 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:3_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array F + + physicaldrive 2I:2:3 + Port: 2I + Box: 2 + Bay: 3 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADB9HEN + WWID: 5000C500CB4FCB81 + Model: HP MB6000JVYZD + Current Temperature (C): 35 + Maximum Temperature (C): 46 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB4FCB83 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:4_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:4_detail.txt new file mode 100644 index 0000000..de3e13b --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/2I:2:4_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array G + + physicaldrive 2I:2:4 + Port: 2I + Box: 2 + Bay: 4 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC60Z + WWID: 5000C500CB7F8709 + Model: HP MB6000JVYZD + Current Temperature (C): 35 + Maximum Temperature (C): 46 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB7F870B + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:1_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:1_detail.txt new file mode 100644 index 0000000..c4b1b00 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:1_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array H + + physicaldrive 3I:3:1 + Port: 3I + Box: 3 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC2HK + WWID: 5000C500CB805B25 + Model: HP MB6000JVYZD + Current Temperature (C): 33 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB805B27 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:2_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:2_detail.txt new file mode 100644 index 0000000..f7cd86b --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:2_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array I + + physicaldrive 3I:3:2 + Port: 3I + Box: 3 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBBFGH + WWID: 5000C500CB811B99 + Model: HP MB6000JVYZD + Current Temperature (C): 34 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB811B9B + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:3_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:3_detail.txt new file mode 100644 index 0000000..d15b7b8 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:3_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array J + + physicaldrive 3I:3:3 + Port: 3I + Box: 3 + Bay: 3 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD6 + Serial Number: WSE1F022 + WWID: 5000C500EF2287B5 + Model: HPE MB006000JWWQN + Current Temperature (C): 34 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 10 hour(s), 20 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500EF2287B7 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:4_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:4_detail.txt new file mode 100644 index 0000000..5167723 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/3I:3:4_detail.txt @@ -0,0 +1,37 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array K + + physicaldrive 3I:3:4 + Port: 3I + Box: 3 + Bay: 4 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADB9VP5 + WWID: 5000C500CB817339 + Model: HP MB6000JVYZD + Current Temperature (C): 36 + Maximum Temperature (C): 47 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB81733B + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/4I:6:1_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/4I:6:1_detail.txt new file mode 100644 index 0000000..9153444 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/4I:6:1_detail.txt @@ -0,0 +1,40 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array A + + physicaldrive 4I:6:1 + Port: 4I + Box: 6 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: Solid State SAS + Size: 800 GB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Firmware Revision: HPD1 + Serial Number: W2X0751Y + WWID: 5000CCA0B8712795 + Model: HPE MO000800JXBEV + Current Temperature (C): 51 + Maximum Temperature (C): 58 + Usage remaining: 93.35% + Power On Hours: 28384 + Estimated Life Remaining based on workload to date: 16601 days + SSD Smart Trip Wearout: False + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 10 minute(s), 0 second(s) + Unrestricted Sanitize Supported: False + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000CCA0B8712794 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/4I:6:2_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/4I:6:2_detail.txt new file mode 100644 index 0000000..3794705 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/4I:6:2_detail.txt @@ -0,0 +1,37 @@ +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array A + + physicaldrive 4I:6:2 + Port: 4I + Box: 6 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: Solid State SAS + Size: 800 GB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Firmware Revision: HPD1 + Serial Number: W2X0WTKY + WWID: 5000CCA0B8725D59 + Model: HPE MO000800JXBEV + Current Temperature (C): 51 + Maximum Temperature (C): 59 + Usage remaining: 93.31% + Power On Hours: 28384 + Estimated Life Remaining based on workload to date: 16495 days + SSD Smart Trip Wearout: False + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 10 minute(s), 0 second(s) + Unrestricted Sanitize Supported: False + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000CCA0B8725D58 + Multi-Actuator Drive: False \ No newline at end of file diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/all.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/all.txt new file mode 100644 index 0000000..77bec5a --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/all.txt @@ -0,0 +1,52 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array A + + physicaldrive 4I:6:1 (port 4I:box 6:bay 1, SAS SSD, 800 GB, OK) + physicaldrive 4I:6:2 (port 4I:box 6:bay 2, SAS SSD, 800 GB, OK) + + Array B + + physicaldrive 1I:1:3 (port 1I:box 1:bay 3, SAS HDD, 6 TB, OK) + + Array C + + physicaldrive 1I:1:4 (port 1I:box 1:bay 4, SAS HDD, 6 TB, OK) + + Array D + + physicaldrive 2I:2:1 (port 2I:box 2:bay 1, SAS HDD, 6 TB, OK) + + Array E + + physicaldrive 2I:2:2 (port 2I:box 2:bay 2, SAS HDD, 6 TB, OK) + + Array F + + physicaldrive 2I:2:3 (port 2I:box 2:bay 3, SAS HDD, 6 TB, OK) + + Array G + + physicaldrive 2I:2:4 (port 2I:box 2:bay 4, SAS HDD, 6 TB, OK) + + Array H + + physicaldrive 3I:3:1 (port 3I:box 3:bay 1, SAS HDD, 6 TB, OK) + + Array I + + physicaldrive 3I:3:2 (port 3I:box 3:bay 2, SAS HDD, 6 TB, OK) + + Array J + + physicaldrive 3I:3:3 (port 3I:box 3:bay 3, SAS HDD, 6 TB, OK) + + Array K + + physicaldrive 3I:3:4 (port 3I:box 3:bay 4, SAS HDD, 6 TB, OK) + + Unassigned + + physicaldrive 1I:1:1 (port 1I:box 1:bay 1, SAS HDD, 2 TB, OK) + physicaldrive 1I:1:2 (port 1I:box 1:bay 2, SAS HDD, 2 TB, OK) \ No newline at end of file diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/all_detail.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/all_detail.txt new file mode 100644 index 0000000..91b64f0 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/all_detail.txt @@ -0,0 +1,483 @@ + +HPE Smart Array P816i-a SR Gen10 in Slot 0 (Embedded) + + Array A + + physicaldrive 4I:6:1 + Port: 4I + Box: 6 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: Solid State SAS + Size: 800 GB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Firmware Revision: HPD1 + Serial Number: W2X0751Y + WWID: 5000CCA0B8712795 + Model: HPE MO000800JXBEV + Current Temperature (C): 51 + Maximum Temperature (C): 58 + Usage remaining: 93.35% + Power On Hours: 28293 + Estimated Life Remaining based on workload to date: 16548 days + SSD Smart Trip Wearout: False + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 10 minute(s), 0 second(s) + Unrestricted Sanitize Supported: False + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000CCA0B8712794 + Multi-Actuator Drive: False + + physicaldrive 4I:6:2 + Port: 4I + Box: 6 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: Solid State SAS + Size: 800 GB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Firmware Revision: HPD1 + Serial Number: W2X0WTKY + WWID: 5000CCA0B8725D59 + Model: HPE MO000800JXBEV + Current Temperature (C): 51 + Maximum Temperature (C): 59 + Usage remaining: 93.32% + Power On Hours: 28293 + Estimated Life Remaining based on workload to date: 16468 days + SSD Smart Trip Wearout: False + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 10 minute(s), 0 second(s) + Unrestricted Sanitize Supported: False + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000CCA0B8725D58 + Multi-Actuator Drive: False + + + Array B + + physicaldrive 1I:1:3 + Port: 1I + Box: 1 + Bay: 3 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBBY88 + WWID: 5000C500CB7F22AD + Model: HP MB6000JVYZD + Current Temperature (C): 33 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB7F22AF + Multi-Actuator Drive: False + + + Array C + + physicaldrive 1I:1:4 + Port: 1I + Box: 1 + Bay: 4 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC3RL + WWID: 5000C500CB800651 + Model: HP MB6000JVYZD + Current Temperature (C): 34 + Maximum Temperature (C): 46 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB800653 + Multi-Actuator Drive: False + + + Array D + + physicaldrive 2I:2:1 + Port: 2I + Box: 2 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC9G0 + WWID: 5000C500CB8131FD + Model: HP MB6000JVYZD + Current Temperature (C): 33 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB8131FF + Multi-Actuator Drive: False + + + Array E + + physicaldrive 2I:2:2 + Port: 2I + Box: 2 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBCAF0 + WWID: 5000C500CB80B755 + Model: HP MB6000JVYZD + Current Temperature (C): 34 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB80B757 + Multi-Actuator Drive: False + + + Array F + + physicaldrive 2I:2:3 + Port: 2I + Box: 2 + Bay: 3 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADB9HEN + WWID: 5000C500CB4FCB81 + Model: HP MB6000JVYZD + Current Temperature (C): 34 + Maximum Temperature (C): 46 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB4FCB83 + Multi-Actuator Drive: False + + + Array G + + physicaldrive 2I:2:4 + Port: 2I + Box: 2 + Bay: 4 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC60Z + WWID: 5000C500CB7F8709 + Model: HP MB6000JVYZD + Current Temperature (C): 34 + Maximum Temperature (C): 46 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB7F870B + Multi-Actuator Drive: False + + + Array H + + physicaldrive 3I:3:1 + Port: 3I + Box: 3 + Bay: 1 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBC2HK + WWID: 5000C500CB805B25 + Model: HP MB6000JVYZD + Current Temperature (C): 35 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB805B27 + Multi-Actuator Drive: False + + + Array I + + physicaldrive 3I:3:2 + Port: 3I + Box: 3 + Bay: 2 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADBBFGH + WWID: 5000C500CB811B99 + Model: HP MB6000JVYZD + Current Temperature (C): 35 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB811B9B + Multi-Actuator Drive: False + + + Array J + + physicaldrive 3I:3:3 + Port: 3I + Box: 3 + Bay: 3 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD6 + Serial Number: WSE1F022 + WWID: 5000C500EF2287B5 + Model: HPE MB006000JWWQN + Current Temperature (C): 34 + Maximum Temperature (C): 45 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 10 hour(s), 20 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500EF2287B7 + Multi-Actuator Drive: False + + + Array K + + physicaldrive 3I:3:4 + Port: 3I + Box: 3 + Bay: 4 + Status: OK + Drive Type: Data Drive + Interface Type: SAS + Size: 6 TB + Drive exposed to OS: False + Logical/Physical Block Size: 512/4096 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: ZADB9VP5 + WWID: 5000C500CB817339 + Model: HP MB6000JVYZD + Current Temperature (C): 36 + Maximum Temperature (C): 47 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 11 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CB81733B + Multi-Actuator Drive: False + + + Unassigned + + physicaldrive 1I:1:1 + Port: 1I + Box: 1 + Bay: 1 + Status: OK + Drive Type: Unassigned Drive + Interface Type: SAS + Size: 2 TB + Drive exposed to OS: True + Logical/Physical Block Size: 512/512 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: WS1061J3 + WWID: 5000C500CBD6D241 + Model: HP MB002000JWWQA + Current Temperature (C): 30 + Maximum Temperature (C): 43 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Disk Name: /dev/sda + Mount Points: None + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 3 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CBD6D243 + Multi-Actuator Drive: False + + physicaldrive 1I:1:2 + Port: 1I + Box: 1 + Bay: 2 + Status: OK + Drive Type: Unassigned Drive + Interface Type: SAS + Size: 2 TB + Drive exposed to OS: True + Logical/Physical Block Size: 512/512 + Rotational Speed: 7200 + Firmware Revision: HPD4 + Serial Number: WS106260 + WWID: 5000C500CBD6D175 + Model: HP MB002000JWWQA + Current Temperature (C): 30 + Maximum Temperature (C): 44 + PHY Count: 2 + PHY Transfer Rate: 12.0Gbps, Unknown + PHY Physical Link Rate: 12.0Gbps, Unknown + PHY Maximum Link Rate: 12.0Gbps, 12.0Gbps + Drive Authentication Status: OK + Carrier Application Version: 11 + Carrier Bootloader Version: 6 + Disk Name: /dev/sdb + Mount Points: None + Sanitize Erase Supported: True + Sanitize Estimated Max Erase Time: 3 hour(s), 40 minute(s) + Unrestricted Sanitize Supported: True + Shingled Magnetic Recording Support: None + Drive Unique ID: 5000C500CBD6D177 + Multi-Actuator Drive: False + + diff --git a/pkg/implementation/physicaldrivegetter/physicaldrives/all_status.txt b/pkg/implementation/physicaldrivegetter/physicaldrives/all_status.txt new file mode 100644 index 0000000..3ffaa99 --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/physicaldrives/all_status.txt @@ -0,0 +1,16 @@ + + physicaldrive 4I:6:1 (port 4I:box 6:bay 1, 800 GB): OK + physicaldrive 4I:6:2 (port 4I:box 6:bay 2, 800 GB): OK + physicaldrive 1I:1:3 (port 1I:box 1:bay 3, 6 TB): OK + physicaldrive 1I:1:4 (port 1I:box 1:bay 4, 6 TB): OK + physicaldrive 2I:2:1 (port 2I:box 2:bay 1, 6 TB): OK + physicaldrive 2I:2:2 (port 2I:box 2:bay 2, 6 TB): OK + physicaldrive 2I:2:3 (port 2I:box 2:bay 3, 6 TB): OK + physicaldrive 2I:2:4 (port 2I:box 2:bay 4, 6 TB): OK + physicaldrive 3I:3:1 (port 3I:box 3:bay 1, 6 TB): OK + physicaldrive 3I:3:2 (port 3I:box 3:bay 2, 6 TB): OK + physicaldrive 3I:3:3 (port 3I:box 3:bay 3, 6 TB): OK + physicaldrive 3I:3:4 (port 3I:box 3:bay 4, 6 TB): OK + physicaldrive 1I:1:1 (port 1I:box 1:bay 1, 2 TB): OK + physicaldrive 1I:1:2 (port 1I:box 1:bay 2, 2 TB): OK + diff --git a/pkg/implementation/physicaldrivegetter/ssacli.go b/pkg/implementation/physicaldrivegetter/ssacli.go new file mode 100644 index 0000000..43436de --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/ssacli.go @@ -0,0 +1,301 @@ +package physicaldrivegetter + +import ( + "regexp" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/commandrunner" + "github.com/scality/raidmgmt/pkg/utils" +) + +const ( + ssacliSlotRegexpPattern = `Slot (\d+)` + ssacliPhysicalDriveRegexpPattern = `physicaldrive\s+(.+)` +) + +type SSACLI struct { + commandrunner.CommandRunner + lsblk commandrunner.CommandRunner +} + +var ( + _ ports.PhysicalDrivesGetter = &SSACLI{} + + ssacliSlotRegexp = regexp.MustCompile(ssacliSlotRegexpPattern) + ssacliPhysicalDriveRegexp = regexp.MustCompile(ssacliPhysicalDriveRegexpPattern) +) + +// NewSSACLI creates a new SSACLI instance. +func NewSSACLI(commandRunner commandrunner.CommandRunner) *SSACLI { + return &SSACLI{ + CommandRunner: commandRunner, + } +} + +// PhysicalDrives returns all physical drives for a given controller. +func (s *SSACLI) PhysicalDrives(metadata *raidcontroller.Metadata) ( + []*physicaldrive.PhysicalDrive, + error, +) { + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.ID), + "physicaldrive", + "all", + "show", + "detail", + } + + output, err := s.CommandRunner.Run(args) + if err != nil { + return nil, errors.Wrap(err, "failed to show all physical drives details") + } + + physicalDrives, err := s.parsePhysicalDrives(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse physical drives details") + } + + return physicalDrives, nil +} + +// PhysicalDrive returns a physical drive for a given metadata. +func (s *SSACLI) PhysicalDrive(metadata *physicaldrive.Metadata) ( + *physicaldrive.PhysicalDrive, + error, +) { + slot := metadata.Slot.Format() + + args := []string{ + "controller", + "slot=" + strconv.Itoa(metadata.CtrlMetadata.ID), + "physicaldrive", + slot, + "show", + "detail", + } + + output, err := s.CommandRunner.Run(args) + if err != nil { + return nil, errors.Wrapf(err, "failed to show details for physical drive %s", slot) + } + + controllerID, err := parseControllerID(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse controller ID") + } + + physicalDrive, err := s.parsePhysicalDrive(output) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse physical drive %s", slot) + } + + physicalDrive.CtrlMetadata.ID = controllerID + + return physicalDrive, nil +} + +// parsePhysicalDrives parses the output of the physicaldrive command and +// returns a list of PhysicalDrive entities. +func (s *SSACLI) parsePhysicalDrives(output []byte) ([]*physicaldrive.PhysicalDrive, error) { + blocks := utils.SplitOutput(ssacliPhysicalDriveRegexp, output) + + physicalDrives := make([]*physicaldrive.PhysicalDrive, 0, len(blocks)) + + controllerID, err := parseControllerID(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse controller ID") + } + + for _, block := range blocks { + physicalDrive, err := s.parsePhysicalDrive(block) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse physical drive: %s", block) + } + + physicalDrive.CtrlMetadata.ID = controllerID + + physicalDrives = append(physicalDrives, physicalDrive) + } + + return physicalDrives, nil +} + +// parseControllerID parses the controller ID from the output of the physicaldrive command. +func parseControllerID(output []byte) (int, error) { + match := ssacliSlotRegexp.FindSubmatch(output) + + if match == nil { + return 0, errors.New("controller ID not found") + } + + controllerID, err := strconv.Atoi(string(match[1])) + if err != nil { + return 0, errors.Wrap(err, "failed to convert controller ID to integer") + } + + return controllerID, nil +} + +// parsePhysicalDrive parses a physical drive block and returns a PhysicalDrive entity. +func (s *SSACLI) parsePhysicalDrive(block []byte) (*physicaldrive.PhysicalDrive, error) { + // Create the PhysicalDrive entity + physicalDrive := &physicaldrive.PhysicalDrive{ + Metadata: &physicaldrive.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{}, + Slot: &physicaldrive.Slot{}, + }, + } + + // Split the block into lines and parse each line + for line := range strings.SplitSeq(string(block), "\n") { + if err := s.parsePDLine(physicalDrive, line); err != nil { + return nil, errors.Wrapf(err, "failed to parse line of physical drive: %s", + strings.TrimSpace(line), + ) + } + } + + return physicalDrive, nil +} + +// parsePDLine parses a line of the physicaldrive command output +// and updates the PhysicalDrive entity. +// nolint: cyclop,gocognit // The switch statement is necessary +// to parse the different key-value pairs. +func (s *SSACLI) parsePDLine( //nolint:funlen // This function is long and not compressible + physicalDrive *physicaldrive.PhysicalDrive, + line string, +) error { + key, value := utils.ParseLineDetail(line) + + // Parse the key-value pair + switch key { + case "Port", "Box", "Bay": + parseSlotInfo(physicalDrive, key, value) + + case "Model": + splitLine := strings.Fields(value) + physicalDrive.Vendor = splitLine[0] + physicalDrive.Model = splitLine[1] + + case "Serial Number": + physicalDrive.Serial = value + + case "Size": + size, err := utils.ConvertSizeBytes(value) + if err != nil { + return errors.Wrap(err, "failed to convert size to bytes") + } + + physicalDrive.Size = size + + case "Status": + if physicalDrive.Status == physicaldrive.PDStatusUnknown { + mapStatus := map[string]physicaldrive.PDStatus{ + "OK": physicaldrive.PDStatusUsed, + "Failed": physicaldrive.PDStatusFailed, + "Offline": physicaldrive.PDStatusFailed, + } + + status, ok := mapStatus[value] + if !ok { + return errors.Errorf("invalid status: %s", value) + } + + physicalDrive.Status = status + } + + case "Drive Type": + if physicalDrive.Status != physicaldrive.PDStatusUsed && + strings.Contains(value, "Unassigned") { + physicalDrive.Status = physicaldrive.PDStatusUnassignedGood + } + + case "Interface Type": + mapInterfaceType := map[string]physicaldrive.DiskType{ + "SATA": physicaldrive.DiskTypeHDD, + "SAS": physicaldrive.DiskTypeHDD, + "Solid State SAS": physicaldrive.DiskTypeSSD, + } + + interfaceType, ok := mapInterfaceType[value] + if !ok { + return errors.Errorf("invalid interface type: %s", value) + } + + physicalDrive.Type = interfaceType + + case "Drive Unique ID": + physicalDrive.ID = value + + case "Disk Name": + physicalDrive.DevicePath = value + + blockDevice, err := s.getBlockDevice(value) + if err != nil { + return errors.Wrapf(err, "failed to get block device for %s", value) + } + + if isBlockDeviceUsed(blockDevice) { + physicalDrive.Status = physicaldrive.PDStatusUsed + } + // TODO miss permanent path + } + + return nil +} + +func (s *SSACLI) getBlockDevice(devicePath string) (*BlockDevice, error) { + output, err := s.lsblk.Run([]string{ + devicePath, + "--paths", + "--bytes", + "--nodeps", + "--output", + "name,rota,size,type,tran,mountpoint,fstype,parttype", + }) + if err != nil { + return nil, errors.Wrap(err, "failed to get block device using lsblk") + } + + blockDevices, err := ParseLSBLKOutput(output) + if err != nil { + return nil, errors.Wrap(err, "failed to parse lsblk command output") + } + + if len(blockDevices) <= 0 { + return nil, errors.Errorf("block device not found: %s", devicePath) + } + + return &blockDevices[0], nil +} + +// parseSlotInfo parses the slot information and updates the PhysicalDrive entity. +func parseSlotInfo(pd *physicaldrive.PhysicalDrive, key, value string) { + switch key { + case "Port": + pd.Slot.Port = value + case "Box": + pd.Slot.Enclosure = value + case "Bay": + pd.Slot.Bay = value + } +} + +// isBlockDeviceUsed checks if a block device is used. +// If the device is mounted or has a filesystem type, it is considered used. +// Otherwise, it is considered unassigned good. +func isBlockDeviceUsed(device *BlockDevice) bool { + if device.MountPoint != "" || device.FilesystemType != "" || device.PartitionType != "" { + return true + } + + return false +} diff --git a/pkg/implementation/physicaldrivegetter/ssacli_test.go b/pkg/implementation/physicaldrivegetter/ssacli_test.go new file mode 100644 index 0000000..d71486c --- /dev/null +++ b/pkg/implementation/physicaldrivegetter/ssacli_test.go @@ -0,0 +1,197 @@ +package physicaldrivegetter + +import ( + "os" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" +) + +type MockCommandRunner struct { + mock.Mock +} + +var testDataPath = "./" + +func (m *MockCommandRunner) Run(args []string) ([]byte, error) { + arguments := m.Called(args) + + return arguments.Get(0).([]byte), arguments.Error(1) +} + +func TestSSACLIPhysicalDrives(t *testing.T) { + mockRunner := new(MockCommandRunner) + + s := &SSACLI{ + CommandRunner: mockRunner, + lsblk: mockRunner, + } + + tests := []struct { + name string + mocking []byte + id int + expectedError bool + }{ + { + name: "nominal case", + mocking: mockOutput("physicaldrives/all_detail"), + id: 0, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup command runner expectations + mockRunner.On("Run", []string{ + "controller", + "slot=" + strconv.Itoa(tt.id), + "physicaldrive", + "all", + "show", + "detail", + }).Return(tt.mocking, nil) + + // Mock the lsblk call for disk device path + lsblkOutput := []byte(`NAME ROTA SIZE TYPE TRAN MOUNTPOINT FSTYPE PARTTYPE +/dev/sda 0 858993459200 disk sata `) + mockRunner.On("Run", mock.AnythingOfType("[]string")).Return(lsblkOutput, nil) + + metadata := &raidcontroller.Metadata{ + ID: tt.id, + } + + physicalDrives, err := s.PhysicalDrives(metadata) + + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, physicalDrives) + } else { + assert.NoError(t, err) + assert.NotEmpty(t, physicalDrives) + assert.Len(t, physicalDrives, 14) + + seen := make(map[string]bool) + + for _, pd := range physicalDrives { + assert.NotEmpty(t, pd.Serial) + assert.NotEmpty(t, pd.Model) + assert.NotEmpty(t, pd.Vendor) + + if seen[pd.Serial] { + t.Errorf("Duplicate physical drive: %s", pd.Serial) + } else { + seen[pd.Serial] = true + } + } + } + }) + } +} + +func TestSSCALIPhysicalDrive(t *testing.T) { + mockRunner := new(MockCommandRunner) + + s := &SSACLI{ + CommandRunner: mockRunner, + } + + tests := []struct { + name string + mocking []byte + metadata *physicaldrive.Metadata + expected *physicaldrive.PhysicalDrive + expectedError bool + }{ + { + name: "nominal case", + mocking: mockOutput("physicaldrives/4I:6:1_detail"), + metadata: &physicaldrive.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{ + ID: 0, + }, + Slot: &physicaldrive.Slot{ + Port: "4I", + Enclosure: "6", + Bay: "1", + }, + }, + expected: &physicaldrive.PhysicalDrive{ + Metadata: &physicaldrive.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{ + ID: 0, + }, + Slot: &physicaldrive.Slot{ + Port: "4I", + Enclosure: "6", + Bay: "1", + }, + }, + Vendor: "HPE", + Model: "MO000800JXBEV", + Serial: "W2X0751Y", + ID: "5000CCA0B8712794", + Size: 858993459200, + Status: physicaldrive.PDStatusUsed, + }, + expectedError: false, + }, + // TODO add more test cases + } + + for _, tt := range tests { + mockRunner.On("Run", []string{ + "controller", + "slot=" + strconv.Itoa(tt.metadata.CtrlMetadata.ID), + "physicaldrive", + tt.metadata.Slot.Format(), + "show", + "detail", + }).Return(tt.mocking, nil) + + lsblkOutput := []byte(`NAME ROTA SIZE TYPE TRAN MOUNTPOINT FSTYPE PARTTYPE +/dev/sda 0 858993459200 disk sata `) + mockRunner.On("Run", mock.AnythingOfType("[]string")).Return(lsblkOutput, nil) + + metadata := &physicaldrive.Metadata{ + CtrlMetadata: &raidcontroller.Metadata{ + ID: tt.metadata.CtrlMetadata.ID, + }, + Slot: &physicaldrive.Slot{ + Port: "4I", + Enclosure: "6", + Bay: "1", + }, + } + + physicalDrive, err := s.PhysicalDrive(metadata) + + if tt.expectedError { + assert.Error(t, err) + assert.Nil(t, physicalDrive) + } else { + assert.NoError(t, err) + assert.NotEmpty(t, physicalDrive) + + assert.Equal(t, tt.expected.ID, physicalDrive.ID) + assert.Equal(t, tt.expected.Serial, physicalDrive.Serial) + assert.Equal(t, tt.expected.Model, physicalDrive.Model) + assert.Equal(t, tt.expected.Vendor, physicalDrive.Vendor) + } + } +} + +func mockOutput(filename string) []byte { + output, err := os.ReadFile(testDataPath + filename + ".txt") + if err != nil { + panic(err) + } + + return output +} diff --git a/pkg/utils/formatter.go b/pkg/utils/formatter.go new file mode 100644 index 0000000..4de7c63 --- /dev/null +++ b/pkg/utils/formatter.go @@ -0,0 +1,60 @@ +package utils + +import ( + "bytes" + "regexp" + "strings" +) + +const keyValueParts = 2 + +// splitOutput splits the output into blocks based on the regular expression. +// TODO add tests. +func SplitOutput(regularExpression *regexp.Regexp, output []byte) [][]byte { + indices := regularExpression.FindAllIndex(output, -1) + if indices == nil { + return nil // No matches found + } + + var blocks [][]byte + + start := 0 + + for i, match := range indices { + if i == 0 { + continue // Skip the first match + } + + block := output[start:match[0]] // everything before the match + if len(block) > 0 { // avoid empty blocks + blocks = append(blocks, bytes.TrimSpace(block)) // trim space here + } + + start = match[0] // Start of the next block is the current match + } + // Add the last block if any + if start < len(output) { + blocks = append(blocks, bytes.TrimSpace(output[start:])) + } + + return blocks +} + +// FIXME Might go in another file +// ParseLineDetail parses a line of the show detail command and returns the key and value. +func ParseLineDetail(line string) (key, value string) { + if line == "" { + return "", "" + } + + splitParts := strings.Split(line, ":") + + if len(splitParts) != keyValueParts { + return "", "" + } + + key = strings.TrimSpace(splitParts[0]) + value = strings.TrimSpace(splitParts[1]) + + return key, value +}