-
Notifications
You must be signed in to change notification settings - Fork 0
feat(storcli2): logical volume drive add (expand) and remove #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
g-carre
merged 4 commits into
feature/storcli2-lv-manager-create-delete
from
feature/storcli2-lv-manager-pd-membership
Jul 1, 2026
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
9c66726
feat(storcli2): logical volume drive add (expand) and remove
g-carre 70f0661
fix(storcli2): drop unused receiver on DeletePDsFromLV
g-carre a40039e
feat(storcli2): physical drive blinker
g-carre eb0c35f
Merge pull request #72 from scality/feature/storcli2-blinker
g-carre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package blinker | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "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" | ||
| "github.com/scality/raidmgmt/pkg/implementation/storcli2" | ||
| ) | ||
|
|
||
| const ( | ||
| // storcli2CmdStart and storcli2CmdStop begin and end a drive locate. | ||
| storcli2CmdStart = "start" | ||
| storcli2CmdStop = "stop" | ||
| // storcli2CmdLocate is the locate (blink) operation token. | ||
| storcli2CmdLocate = "locate" | ||
| // storcli2EnclosureSelector and storcli2NoEnclosureSelector address a single | ||
| // drive, with or without an enclosure component. | ||
| storcli2EnclosureSelector = "/c%d/e%s/s%s" | ||
| storcli2NoEnclosureSelector = "/c%d/s%s" | ||
| ) | ||
|
|
||
| // StorCLI2 blinks physical drives through a storcli2/perccli2 command runner. A | ||
| // single implementation serves both binaries; the concrete runner is injected | ||
| // at construction time. | ||
| type StorCLI2 struct { | ||
| runner commandrunner.CommandRunner | ||
| } | ||
|
|
||
| var _ ports.Blinker = &StorCLI2{} | ||
|
|
||
| // NewStorCLI2 returns a blinker backed by the given storcli2 / perccli2 command | ||
| // runner. | ||
| func NewStorCLI2(runner commandrunner.CommandRunner) *StorCLI2 { | ||
| return &StorCLI2{ | ||
| runner: runner, | ||
| } | ||
| } | ||
|
|
||
| // StartBlink starts locating (blinking) a physical drive. | ||
| func (s *StorCLI2) StartBlink(metadata *physicaldrive.Metadata) error { | ||
| if err := s.locate(metadata, storcli2CmdStart); err != nil { | ||
| return errors.Wrap(err, "failed to start blinking physical drive") | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // StopBlink stops locating (blinking) a physical drive. | ||
| func (s *StorCLI2) StopBlink(metadata *physicaldrive.Metadata) error { | ||
| if err := s.locate(metadata, storcli2CmdStop); err != nil { | ||
| return errors.Wrap(err, "failed to stop blinking physical drive") | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // locate runs "<start|stop> locate" on a drive selector and surfaces the | ||
| // in-JSON failure that storcli2 may report regardless of its exit code. | ||
| func (s *StorCLI2) locate(metadata *physicaldrive.Metadata, action string) error { | ||
| selector, err := storcli2SelectorPD(metadata) | ||
| if err != nil { | ||
| return errors.Wrap(err, "failed to build drive selector") | ||
| } | ||
|
|
||
| output, err := s.runner.Run([]string{selector, action, storcli2CmdLocate}) | ||
| if err != nil { | ||
| return errors.Wrapf(err, "failed to run %s locate command", action) | ||
| } | ||
|
|
||
| if _, err := storcli2.Decode(output); err != nil { | ||
| return errors.Wrapf(err, "%s locate command failed", action) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // storcli2SelectorPD builds the storcli2 selector for a drive, choosing the | ||
| // enclosure or no-enclosure form from its parsed slot. | ||
| func storcli2SelectorPD(metadata *physicaldrive.Metadata) (string, error) { | ||
| slot, err := physicaldrive.ParseSlot(metadata.ID) | ||
| if err != nil { | ||
| return "", errors.Wrapf(err, "failed to parse slot %s", metadata.ID) | ||
| } | ||
|
|
||
| if slot.Enclosure != "" { | ||
| return fmt.Sprintf( | ||
| storcli2EnclosureSelector, metadata.CtrlMetadata.ID, slot.Enclosure, slot.Bay, | ||
| ), nil | ||
| } | ||
|
|
||
| return fmt.Sprintf(storcli2NoEnclosureSelector, metadata.CtrlMetadata.ID, slot.Bay), nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| package blinker_test | ||
|
|
||
| import ( | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "github.com/stretchr/testify/mock" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" | ||
| "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" | ||
| "github.com/scality/raidmgmt/pkg/implementation/blinker" | ||
| ) | ||
|
|
||
| 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) | ||
| } | ||
|
|
||
| // storcli2Fixture reads a storcli2 JSON fixture from the package testdata. | ||
| func storcli2Fixture(t *testing.T, name string) []byte { | ||
| t.Helper() | ||
|
|
||
| data, err := os.ReadFile("testdata/storcli2/" + name) | ||
| require.NoError(t, err) | ||
|
|
||
| return data | ||
| } | ||
|
|
||
| // storcli2PDMetadata builds physical-drive metadata with the given EID:Slt id. | ||
| func storcli2PDMetadata(id string) *physicaldrive.Metadata { | ||
| return &physicaldrive.Metadata{ | ||
| CtrlMetadata: &raidcontroller.Metadata{ID: 0}, | ||
| ID: id, | ||
| } | ||
| } | ||
|
|
||
| // TestStorCLI2Blink covers the start/stop locate happy paths, including the | ||
| // enclosure and no-enclosure selector forms. | ||
| func TestStorCLI2Blink(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| id string | ||
| selector string | ||
| action string | ||
| fixture string | ||
| start bool | ||
| }{ | ||
| {name: "start with enclosure", id: "252:0", selector: "/c0/e252/s0", action: "start", fixture: "start.json", start: true}, | ||
| {name: "stop with enclosure", id: "252:0", selector: "/c0/e252/s0", action: "stop", fixture: "stop.json", start: false}, | ||
| {name: "start without enclosure", id: "5", selector: "/c0/s5", action: "start", fixture: "start.json", start: true}, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| mockRunner := new(MockCommandRunner) | ||
| mockRunner.On("Run", []string{tt.selector, tt.action, "locate"}). | ||
| Return(storcli2Fixture(t, tt.fixture), nil) | ||
|
|
||
| b := blinker.NewStorCLI2(mockRunner) | ||
|
|
||
| var err error | ||
| if tt.start { | ||
| err = b.StartBlink(storcli2PDMetadata(tt.id)) | ||
| } else { | ||
| err = b.StopBlink(storcli2PDMetadata(tt.id)) | ||
| } | ||
|
|
||
| require.NoError(t, err) | ||
| mockRunner.AssertExpectations(t) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // TestStorCLI2BlinkCommandError pins that a runner failure is surfaced. | ||
| func TestStorCLI2BlinkCommandError(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| mockRunner := new(MockCommandRunner) | ||
| mockRunner.On("Run", []string{"/c0/e252/s0", "start", "locate"}). | ||
| Return([]byte(nil), errors.New("boom")) | ||
|
|
||
| b := blinker.NewStorCLI2(mockRunner) | ||
|
|
||
| err := b.StartBlink(storcli2PDMetadata("252:0")) | ||
| require.Error(t, err) | ||
| } | ||
|
|
||
| // TestStorCLI2BlinkInvalidSlot pins that an unparseable drive id is rejected | ||
| // before any command is run. | ||
| func TestStorCLI2BlinkInvalidSlot(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| mockRunner := new(MockCommandRunner) | ||
|
|
||
| b := blinker.NewStorCLI2(mockRunner) | ||
|
|
||
| err := b.StopBlink(storcli2PDMetadata("")) | ||
| require.Error(t, err) | ||
| mockRunner.AssertNotCalled(t, "Run") | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
pkg/implementation/logicalvolumemanager/testdata/storcli2/expand/fail.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "Controllers":[ | ||
| { | ||
| "Command Status" : { | ||
| "CLI Version" : "008.0005.0000.0010 Feb 22, 2023", | ||
| "Operating system" : "Linux4.18.0-553.115.1.el8_10.x86_64", | ||
| "Controller" : "0", | ||
| "Status" : "Failure", | ||
| "Description" : "expand VD failed", | ||
| "Detailed Status" : [ | ||
| { | ||
| "VD" : 25, | ||
| "Status" : "Failed", | ||
| "ErrType" : "CLI", | ||
| "ErrCd" : 255, | ||
| "ErrMsg" : "Expansion not supported on this VD" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ] | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test for the
storcli2.Decodefailure path inlocate(). The runner-error case (TestStorCLI2BlinkCommandError) and the invalid-slot case are covered, but there is no test where the runner succeeds and the JSON payload contains"Status": "Failure"— i.e. thestorcli2.Decodeerror branch at line 77 ofstorcli2.go. The logicalvolumemanager tests cover this pattern forexpand(TestStorCLI2AddPDsToLVCommandErrorwithexpand/fail.json); the blinker should have an analogous fixture (e.g.testdata/storcli2/start_fail.json) and test.— Claude Code