package driver
import (
"strconv"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gitcode.com/openFuyao/ub-ssu-csi/pkg/backend"
)
const raid5MinNamespaceNumber = 3
var validChunkSizes = map[uint32]bool{
4: true, 16: true, 32: true, 64: true, 128: true, 256: true, 512: true,
}
var validRaidLevels = map[uint8]bool{
0: true, 5: true,
}
type stageVolumeContext struct {
volumeID string
deviceID string
devicePath string
chunkSize uint32
raidLevel uint8
connect func() error
disconnect func() error
}
func (ns *NodeServer) prepareStageVolumeContext(volumeID string, volumeCtx map[string]string) (*stageVolumeContext, error) {
deviceID := volumeCtx[ctxKeyDeviceID]
strategy := volumeCtx[ctxKeyAllocStrategy]
switch strategy {
case backend.AllocStrategyNormal:
if deviceID == "" {
return nil, status.Error(codes.InvalidArgument, "deviceID must be specified for normal allocation strategy")
}
stageCtx := &stageVolumeContext{
volumeID: volumeID,
deviceID: deviceID,
devicePath: "/dev/disk/by-id/nvme-" + deviceID,
}
bindNormalConnector(ns, stageCtx)
return stageCtx, nil
case backend.AllocStrategyStriped:
nsnum, err := parseNsnum(volumeCtx[ctxKeyNsnum])
if err != nil {
return nil, err
}
raidLevel, err := parseRaidLevel(volumeCtx[ctxKeyRaidLevel])
if err != nil {
return nil, err
}
if err := validateStripingNsnum(raidLevel, nsnum); err != nil {
return nil, err
}
chunkSize, err := parseChunkSize(volumeCtx[ctxKeyChunkSize])
if err != nil {
return nil, err
}
stageCtx := &stageVolumeContext{
volumeID: volumeID,
devicePath: "/dev/" + volumeID,
raidLevel: raidLevel,
chunkSize: chunkSize,
}
bindStripingConnector(ns, stageCtx)
return stageCtx, nil
case backend.AllocStrategyLinear:
stageCtx := &stageVolumeContext{
volumeID: volumeID,
devicePath: "/dev/" + volumeID,
}
bindLinearConnector(ns, stageCtx)
return stageCtx, nil
default:
return nil, status.Errorf(codes.InvalidArgument, "unsupported allocStrategy %q", strategy)
}
}
func bindNormalConnector(ns *NodeServer, stageCtx *stageVolumeContext) {
c := ns.connector
stageCtx.connect = func() error { return c.Connect(stageCtx.volumeID, ns.hostNqn, stageCtx.deviceID) }
stageCtx.disconnect = func() error { return c.Disconnect(stageCtx.volumeID, ns.hostNqn) }
}
func bindLinearConnector(ns *NodeServer, stageCtx *stageVolumeContext) {
c := ns.connector
stageCtx.connect = func() error { return c.ConnectWithLinearAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath) }
stageCtx.disconnect = func() error {
return c.DisconnectWithLinearAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath)
}
}
func bindStripingConnector(ns *NodeServer, stageCtx *stageVolumeContext) {
c := ns.connector
stageCtx.connect = func() error {
return c.ConnectWithStripingAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath, stageCtx.raidLevel, stageCtx.chunkSize)
}
stageCtx.disconnect = func() error {
return c.DisconnectWithStripingAddressing(stageCtx.volumeID, ns.hostNqn, stageCtx.devicePath)
}
}
func validateStripingNsnum(raidLevel uint8, nsnum uint32) error {
if raidLevel == 5 && nsnum > 0 && nsnum < raid5MinNamespaceNumber {
return status.Error(codes.InvalidArgument, "raidLevel 5 requires nsnum at least 3")
}
return nil
}
func parseRaidLevel(value string) (uint8, error) {
parsed, err := strconv.ParseUint(value, 10, 8)
if err != nil || !validRaidLevels[uint8(parsed)] {
return 0, status.Errorf(codes.InvalidArgument, "unsupported raidLevel %q", value)
}
return uint8(parsed), nil
}
func parseChunkSize(value string) (uint32, error) {
if value == "" {
return 0, status.Error(codes.InvalidArgument, "chunkSize must be specified for striping volume")
}
parsed, err := strconv.ParseUint(value, 10, 32)
if err != nil {
return 0, status.Errorf(codes.InvalidArgument, "unsupported chunkSize %q", value)
}
chunkSize := uint32(parsed)
if !validChunkSizes[chunkSize] {
return 0, status.Errorf(codes.InvalidArgument, "unsupported chunkSize %q", value)
}
return chunkSize, nil
}
func parseNsnum(value string) (uint32, error) {
if value == "" {
return 0, nil
}
parsed, err := strconv.ParseUint(value, 10, 32)
if err != nil || parsed == 0 {
return 0, status.Error(codes.InvalidArgument, "nsnum must be positive")
}
return uint32(parsed), nil
}