diff --git a/DESIGN.md b/DESIGN.md index ab602ea..0a30937 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -177,10 +177,15 @@ Design notes, verified against the StorCLI2 User Guide and a live MegaRAID - Drive states come in suffixed variants (`Shld`, `Sntz`, `Dgrd`), so they are matched by family; a `Failed`/`Offline`/`Missing` status takes precedence over any state. -- storcli2 dropped some storcli operations: there is no RAID-level migration - (drive *removal* from a volume is not possible; adding drives goes through - `/cx/vx expand drives=`), and no IO policy (`Cached`/`Direct`) cache - option -- the IO policy of parsed volumes is always `Unknown`. +- storcli2 dropped some storcli operations: there is no RAID-level migration, + so a volume's member set can no longer be *shrunk* (storcli's `start migrate + option=remove`); growing a volume moves to `/cx/vx expand drives=`. This is + about reshaping the array's member **count** only -- replacing a *failed* + drive (rebuild onto a hot spare or a hot-swapped replacement, copyback) keeps + the member count unchanged, is a separate command family that storcli2 still + exposes, and is out of scope for raidmgmt regardless of controller. There is + also no IO policy (`Cached`/`Direct`) cache option -- the IO policy of parsed + volumes is always `Unknown`. The read path (controller, physical drive and logical volume getters), the cache and JBOD setters (`lvcachesetter`, `jbodsetter`), the shared @@ -197,17 +202,18 @@ storcli in several places). | `CreateLV` | `/cx add vd r [Size=] drives=e:s,... [WT\|WB\|AWB] [nora\|ra]` | Cache policies are bare tokens at creation time; storcli's `type=` / `wrcache=` / `rdpolicy=` forms are gone. | | `DeleteLV` | `/cx/vx delete [discardcache] [force]` | A nonexistent VD yields a failure payload surfaced by `Decode`. | | `AddPDsToLV` | `/cx/vx expand drives=e:s,...` | Online capacity expansion. Documented and present in the binary help, but not exercised on hardware yet; progress is visible through `/cx/vx show expansion` and `show ocedriveinfo`. | -| `DeletePDsFromLV` | -- | Not supported: the official command map drops `start migrate` with no replacement for `option=remove`. Must return `ErrFunctionNotSupportedByImplementation`. | +| `DeletePDsFromLV` | -- | Not supported: removing a member (shrinking a volume) used storcli's `start migrate option=remove`, which the storcli2 command map drops with no replacement. This is array *reshaping*, not failed-drive replacement (rebuild / hot-spare / copyback), which keeps the member count and is a separate, still-supported command family out of scope here. Returns `ErrFunctionNotSupportedByImplementation`. | | `SetLVCacheOptions` | `/cx/vx set rdcache=RA\|NoRA` and `/cx/vx set wrcache=WT\|WB\|AWB` | Two separate commands: storcli's combined syntax is rejected. The IO policy cannot be set (see above); beware that `CacheOptions.Validate()` rejects an unknown IO policy, so a request cannot be round-tripped from getter output as-is. | | `EnableJBOD` | `/cx/ex/sx set jbod [force]` | Converts the drive **state**; the drive status is unchanged. | | `DisableJBOD` | `/cx/ex/sx set uconf [force]` | storcli's `delete jbod` no longer parses; `set good` would only change the status. | | `StartBlink` / `StopBlink` | `/cx/ex/sx start locate` / `stop locate` | Same grammar as storcli. | -Once every port is covered, a top-level `raidcontroller.StorCLI2` composition -embeds the seven components (no stubs: every operation except -`DeletePDsFromLV` is supported); a single composition serves both binaries -since only the injected runner differs. Until then the components are wired -individually. +The top-level `raidcontroller.StorCLI2` composition embeds the storcli2 +components across the full `ports.RAIDController` surface (no stubs: every +operation except `DeletePDsFromLV` is supported, and that unsupported error is +returned by the logical-volume manager itself, not by a composition-level +override). A single composition serves both binaries since only the injected +runner differs. > **Note:** Part of the pre-staged write-path fixtures under > `pkg/implementation/lvcachesetter/testdata/storcli2/cacheoptions/` and diff --git a/pkg/implementation/raidcontroller/storcli2.go b/pkg/implementation/raidcontroller/storcli2.go new file mode 100644 index 0000000..54bd0c9 --- /dev/null +++ b/pkg/implementation/raidcontroller/storcli2.go @@ -0,0 +1,52 @@ +package raidcontroller + +import ( + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/blinker" + "github.com/scality/raidmgmt/pkg/implementation/commandrunner" + "github.com/scality/raidmgmt/pkg/implementation/controllergetter" + "github.com/scality/raidmgmt/pkg/implementation/jbodsetter" + "github.com/scality/raidmgmt/pkg/implementation/logicalvolumegetter" + "github.com/scality/raidmgmt/pkg/implementation/logicalvolumemanager" + "github.com/scality/raidmgmt/pkg/implementation/lvcachesetter" + "github.com/scality/raidmgmt/pkg/implementation/physicaldrivegetter" +) + +// StorCLI2 is the top-level RAID controller adapter for MegaRAID/PERC +// controllers driven by storcli2/perccli2. It composes the storcli2 components +// into the full ports.RAIDController surface. A single composition serves both +// binaries since only the injected command runner differs. Unlike SmartArray, +// storcli2 supports every operation, so there are no +// ErrFunctionNotSupportedByImplementation stubs. +type StorCLI2 struct { + ports.ControllersGetter + ports.PhysicalDrivesGetter + ports.LogicalVolumesGetter + ports.LogicalVolumesManager + ports.LVCacheSetter + ports.JBODSetter + ports.Blinker +} + +var _ ports.RAIDController = &StorCLI2{} + +// NewStorCLI2 wires the storcli2 components on top of the given command runner. +// One component package implements each port; the logical-volume manager and +// the cache setter both read current state through the logical-volume getter, +// which is therefore shared rather than constructed twice. +func NewStorCLI2(runner commandrunner.CommandRunner) *StorCLI2 { + physicalDrivesGetter := physicaldrivegetter.NewStorCLI2(runner) + logicalVolumesGetter := logicalvolumegetter.NewStorCLI2(runner) + + return &StorCLI2{ + ControllersGetter: controllergetter.NewStorCLI2(runner), + PhysicalDrivesGetter: physicalDrivesGetter, + LogicalVolumesGetter: logicalVolumesGetter, + LogicalVolumesManager: logicalvolumemanager.NewStorCLI2( + runner, physicalDrivesGetter, logicalVolumesGetter, + ), + LVCacheSetter: lvcachesetter.NewStorCLI2(runner, logicalVolumesGetter), + JBODSetter: jbodsetter.NewStorCLI2(runner), + Blinker: blinker.NewStorCLI2(runner), + } +} diff --git a/pkg/implementation/raidcontroller/storcli2_test.go b/pkg/implementation/raidcontroller/storcli2_test.go new file mode 100644 index 0000000..f854f98 --- /dev/null +++ b/pkg/implementation/raidcontroller/storcli2_test.go @@ -0,0 +1,54 @@ +package raidcontroller_test + +import ( + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/scality/raidmgmt/pkg/domain/entities/physicaldrive" + rcentity "github.com/scality/raidmgmt/pkg/domain/entities/raidcontroller" + "github.com/scality/raidmgmt/pkg/domain/ports" + "github.com/scality/raidmgmt/pkg/implementation/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) +} + +const storcli2LocateSuccess = `{ +"Controllers":[ +{ + "Command Status" : { + "Status" : "Success", + "Description" : "Start PD Locate Succeeded." + } +} +] +}` + +// TestStorCLI2Composition is a smoke test: it wires the full adapter on a mocked +// runner, asserts the composition satisfies ports.RAIDController, and drives one +// operation end to end to prove the components are reachable through the runner. +func TestStorCLI2Composition(t *testing.T) { + t.Parallel() + + mockRunner := new(MockCommandRunner) + mockRunner.On("Run", []string{"/c0/e252/s0", "start", "locate"}). + Return([]byte(storcli2LocateSuccess), nil) + + var adapter ports.RAIDController = raidcontroller.NewStorCLI2(mockRunner) + + err := adapter.StartBlink(&physicaldrive.Metadata{ + CtrlMetadata: &rcentity.Metadata{ID: 0}, + ID: "252:0", + }) + require.NoError(t, err) + mockRunner.AssertExpectations(t) +}