Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,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
shared envelope/decoder and both command runners are implemented. The
Expand All @@ -180,17 +185,18 @@ storcli in several places).
| `CreateLV` | `/cx add vd r<level> [Size=<sz>] 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/logicalvolumemanager/testdata/storcli2/` and
Expand Down
52 changes: 52 additions & 0 deletions pkg/implementation/raidcontroller/storcli2.go
Original file line number Diff line number Diff line change
@@ -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/logicalvolumegetter"
"github.com/scality/raidmgmt/pkg/implementation/logicalvolumemanager"
"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.
// The physical-drive getter also serves as the JBOD setter and the logical-
// volume manager also serves as the cache setter, so those components are
// shared rather than constructed twice; the manager reads current state through
// the physical-drive and logical-volume getters.
func NewStorCLI2(runner commandrunner.CommandRunner) *StorCLI2 {
physicalDrivesGetter := physicaldrivegetter.NewStorCLI2(runner)
logicalVolumesGetter := logicalvolumegetter.NewStorCLI2(runner)
logicalVolumesManager := logicalvolumemanager.NewStorCLI2(
runner, physicalDrivesGetter, logicalVolumesGetter,
)

return &StorCLI2{
ControllersGetter: controllergetter.NewStorCLI2(runner),
PhysicalDrivesGetter: physicalDrivesGetter,
LogicalVolumesGetter: logicalVolumesGetter,
LogicalVolumesManager: logicalVolumesManager,
LVCacheSetter: logicalVolumesManager,
JBODSetter: physicalDrivesGetter,
Blinker: blinker.NewStorCLI2(runner),
}
}
54 changes: 54 additions & 0 deletions pkg/implementation/raidcontroller/storcli2_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading