Files
cloudpods/pkg/baremetal/utils/raid/adaptec/adaptec.go
2022-10-28 19:53:03 +08:00

661 lines
17 KiB
Go

// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package adaptec
import (
"fmt"
"regexp"
"strconv"
"strings"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/stringutils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/baremetal/utils/raid"
"yunion.io/x/onecloud/pkg/compute/baremetal"
"yunion.io/x/onecloud/pkg/util/regutils2"
)
func init() {
raid.RegisterDriver(api.DISK_DRIVER_ADAPTECRAID, NewAdaptecRaid)
}
const (
ControllerModeRaidExposeRaw = "RAID (Expose RAW)"
ControllerModeRaidHideRaw = "RAID (Hide RAW)"
ControllerModeMixed = "Mixed"
)
type AdaptecRaid struct {
term raid.IExecTerm
adapters []*AdaptecRaidAdaptor
}
func NewAdaptecRaid(term raid.IExecTerm) raid.IRaidDriver {
return &AdaptecRaid{
term: term,
adapters: make([]*AdaptecRaidAdaptor, 0),
}
}
func GetCommand(args ...string) string {
bin := "/opt/adaptec/arcconf"
return raid.GetCommand(bin, args...)
}
func (r *AdaptecRaid) GetName() string {
return baremetal.DISK_DRIVER_ADAPTECRAID
}
func (r *AdaptecRaid) ParsePhyDevs() error {
cmd := GetCommand("LIST")
ret, err := r.term.Run(cmd)
if err != nil {
return errors.Wrap(err, "list controllers")
}
if err := r.parsePhyDevs(ret); err != nil {
return errors.Wrap(err, "parse physical device")
}
if len(r.adapters) == 0 {
return errors.Errorf("Not found adaptec raid controller")
}
return nil
}
func (r *AdaptecRaid) CleanRaid() error {
for _, ada := range r.adapters {
ada.removeJBODDisks()
ada.RemoveLogicVolumes()
}
return nil
}
func (r *AdaptecRaid) GetAdapters() []raid.IRaidAdapter {
ret := make([]raid.IRaidAdapter, 0)
for _, a := range r.adapters {
ret = append(ret, a)
}
return ret
}
func (r *AdaptecRaid) PreBuildRaid(confs []*api.BaremetalDiskConfig, adapterIdx int) error {
return nil
}
var (
adaptorIDPatter = regexp.MustCompile(`Controller (?P<idx>[0-9]+):`)
)
func getAdaptorIndex(line string) int {
adapStr := regutils2.GetParams(adaptorIDPatter, line)["idx"]
if adapStr == "" {
return -1
}
adapInt, err := strconv.Atoi(adapStr)
if err != nil {
log.Errorf("Parse adapator string %q id error: %v", line, err)
return -1
}
return adapInt
}
func (r *AdaptecRaid) parsePhyDevs(lines []string) error {
for _, line := range lines {
index := getAdaptorIndex(line)
if index == -1 {
continue
}
ada, err := NewAdaptecRaidAdaptor(index, r)
if err != nil {
return errors.Wrapf(err, "New raid adaptor %d", index)
}
r.adapters = append(r.adapters, ada)
}
return nil
}
type AdaptecRaidAdaptor struct {
*adaptorInfo
index int
raid *AdaptecRaid
devs []*AdaptecRaidPhyDev
}
var (
_ raid.IRaidAdapter = new(AdaptecRaidAdaptor)
)
func NewAdaptecRaidAdaptor(index int, raid *AdaptecRaid) (*AdaptecRaidAdaptor, error) {
adaptor := AdaptecRaidAdaptor{
index: index,
raid: raid,
}
if err := adaptor.fillInfo(); err != nil {
return nil, errors.Wrapf(err, "fill adaptor %d info", adaptor.index)
}
if err := adaptor.fillDevices(); err != nil {
return nil, errors.Wrap(err, "fill physical devices")
}
/*
* if !adaptor.isRaidHideRawMode() {
* if err := adaptor.setControllerModeRaidHideRaw(); err != nil {
* return nil, errors.Wrap(err, "set controller mode to raid hide raw")
* }
* }
*/
return &adaptor, nil
}
func (ada *AdaptecRaidAdaptor) GetIndex() int {
return ada.index
}
func (ada *AdaptecRaidAdaptor) PreBuildRaid(confs []*api.BaremetalDiskConfig) error {
if !ada.isRaidHideRawMode() {
if err := ada.setControllerModeRaidHideRaw(); err != nil {
return errors.Wrapf(err, "set controller mode to raid hide raw mode")
}
}
if err := ada.removeJBODDisks(); err != nil {
log.Warningf("remove all JBOD disks: %v", err)
}
// uninitialize all device to ready state
if err := ada.uninitializeAllDevice(); err != nil {
// return errors.Wrap(err, "uninitialize all devices")
log.Warningf("uninitializeAllDevice error: %v", err)
}
return nil
}
func (r *AdaptecRaidAdaptor) PostBuildRaid() error {
return nil
}
func (ada *AdaptecRaidAdaptor) getInitializeCmd(args ...string) string {
newArgs := []string{"TASK", "START", fmt.Sprintf("%d", ada.GetIndex()), "DEVICE"}
newArgs = append(newArgs, args...)
newArgs = append(newArgs, "INITIALIZE", "noprompt")
return GetCommand(newArgs...)
}
func (ada *AdaptecRaidAdaptor) initializeDevice(channel string, id string) error {
cmd := ada.getInitializeCmd(channel, id)
_, err := ada.remoteRun(fmt.Sprintf("initialize device %s:%s", channel, id), cmd)
if err != nil {
return err
}
return nil
}
func (ada *AdaptecRaidAdaptor) initializeAllDevice() error {
cmd := ada.getInitializeCmd("ALL")
_, err := ada.remoteRun("initialize all device", cmd)
if err != nil {
return err
}
return nil
}
func (ada *AdaptecRaidAdaptor) uninitializeDevice(channel string, id string) error {
cmd := ada.getUninitializeCmd(channel, id)
_, err := ada.remoteRun(fmt.Sprintf("uninitialize device %s:%s", channel, id), cmd)
if err != nil {
return err
}
return nil
}
func (ada *AdaptecRaidAdaptor) getUninitializeCmd(args ...string) string {
newArgs := []string{"TASK", "START", fmt.Sprintf("%d", ada.GetIndex()), "DEVICE"}
newArgs = append(newArgs, args...)
newArgs = append(newArgs, "UNINITIALIZE", "noprompt")
return GetCommand(newArgs...)
}
func (ada *AdaptecRaidAdaptor) uninitializeAllDevice() error {
cmd := ada.getUninitializeCmd("ALL")
_, err := ada.remoteRun("uninitialize all devices", cmd)
if err != nil {
return err
}
return nil
}
func (ada *AdaptecRaidAdaptor) getTerm() raid.IExecTerm {
return ada.raid.term
}
func (ada *AdaptecRaidAdaptor) remoteRun(hint string, cmd string) ([]string, error) {
out, err := ada.getTerm().Run(cmd)
if err != nil {
return out, errors.Wrapf(err, "%q, out: %v", hint, out)
}
log.Debugf("remote run cmd %s %q successfully: %v", hint, cmd, out)
return out, nil
}
func (ada *AdaptecRaidAdaptor) fillInfo() error {
cmd := GetCommand("GETCONFIG", fmt.Sprintf("%d", ada.index), "AD")
ret, err := ada.remoteRun("get AD config", cmd)
if err != nil {
return err
}
info, err := getAdaptorInfo(ret)
if err != nil {
return errors.Wrap(err, "get adaptor info")
}
ada.adaptorInfo = info
return nil
}
func (ada *AdaptecRaidAdaptor) fillDevices() error {
cmd := GetCommand("GETCONFIG", fmt.Sprintf("%d", ada.index), "PD")
ret, err := ada.remoteRun("get PD config", cmd)
if err != nil {
return err
}
devs := getPhyDevices(ada.GetIndex(), ret)
ada.devs = append(ada.devs, devs...)
return nil
}
func getPhyDevices(adapter int, lines []string) []*AdaptecRaidPhyDev {
dev := newPhyDev(adapter)
devs := make([]*AdaptecRaidPhyDev, 0)
for _, line := range lines {
if dev.parseLine(line) && dev.isComplete() {
tmpDev := dev
devs = append(devs, tmpDev)
dev = newPhyDev(adapter)
}
}
return devs
}
func (ada *AdaptecRaidAdaptor) getChannelId(storage *baremetal.BaremetalStorage) (string, string, error) {
if storage.Addr == "" {
return "", "", errors.Errorf("storage %#v addr is empty", storage)
}
parts := strings.Split(storage.Addr, ":")
if len(parts) != 2 {
return "", "", errors.Errorf("invalid storage %#v addr %q", storage, storage.Addr)
}
return parts[0], parts[1], nil
}
func (ada *AdaptecRaidAdaptor) getCreateCmd(args ...string) string {
newArgs := []string{"CREATE", fmt.Sprintf("%d", ada.GetIndex())}
newArgs = append(newArgs, args...)
return GetCommand(newArgs...)
}
// setControllerMode change adapter controller's mode
//
// Controller Modes : 0 - RAID: Expose RAW
// : 1 - Auto Volume Mode
// : 2 - HBA Mode
// : 3 - RAID: Hide RAW
// : 4 - Simple Volume Mode
// : 5 - Mixed
func (ada *AdaptecRaidAdaptor) setControllerMode(mode int) error {
cmd := GetCommand("SETCONTROLLERMODE", fmt.Sprintf("%d", ada.GetIndex()), fmt.Sprintf("%d", mode), "noprompt")
if _, err := ada.remoteRun(fmt.Sprintf("set controller mode to %d", mode), cmd); err != nil {
return err
}
return nil
}
func (ada *AdaptecRaidAdaptor) setControllerModeRaidExposeRaw() error {
if err := ada.setControllerMode(0); err != nil {
return errors.Wrap(err, "mode raid expose raw")
}
return nil
}
func (ada *AdaptecRaidAdaptor) setControllerModeRaidHideRaw() error {
if err := ada.setControllerMode(3); err != nil {
return errors.Wrap(err, "mode raid hide raw")
}
return nil
}
func (ada *AdaptecRaidAdaptor) setControllerModeMixed() error {
if err := ada.setControllerMode(5); err != nil {
return errors.Wrap(err, "mode mixed")
}
return nil
}
func (ada *AdaptecRaidAdaptor) buildJBOD(dev *baremetal.BaremetalStorage) error {
channel, id, err := ada.getChannelId(dev)
if err != nil {
return errors.Wrap(err, "get channel and id")
}
cmd := ada.getCreateCmd("JBOD", channel, id, "noprompt")
if out, err := ada.remoteRun("create JBOD", cmd); err != nil {
return errors.Wrapf(err, "run cmd %q, out: %q", cmd, out)
}
return nil
}
func (ada *AdaptecRaidAdaptor) buildNonRaid(dev *baremetal.BaremetalStorage) error {
// set to raid expose raw mode or mixed mode
if !ada.isRaidExposeRawMode() && !ada.isMixedMode() {
err := func() error {
errs := []error{}
if err := ada.setControllerModeRaidExposeRaw(); err != nil {
errs = append(errs, err)
} else {
return nil
}
if err := ada.setControllerModeMixed(); err != nil {
errs = append(errs, err)
} else {
return nil
}
return errors.NewAggregate(errs)
}()
if err != nil {
return errors.Wrap(err, "set raid to expose raw or mixed mode")
}
}
// try build JBOD firstly
if err := ada.buildJBOD(dev); err != nil {
log.Warningf("try build JBOD error: %v", err)
} else {
return nil
}
// just uninitialize device when build JBOD fail
channel, id, err := ada.getChannelId(dev)
if err != nil {
return err
}
if err := ada.uninitializeDevice(channel, id); err != nil {
return errors.Wrapf(err, "uninitialize device %v when build jbod fail", dev)
}
return nil
}
func (ada *AdaptecRaidAdaptor) BuildNoneRaid(devs []*baremetal.BaremetalStorage) error {
for _, dev := range devs {
if err := ada.buildNonRaid(dev); err != nil {
return errors.Wrap(err, "build nonraid")
}
}
return nil
}
func (ada *AdaptecRaidAdaptor) getBuildRaidCmd(level int, devs []*baremetal.BaremetalStorage) (string, error) {
if len(devs) == 0 {
return "", errors.Errorf("devices is empty")
}
args := []string{"LOGICALDRIVE", "MAX", fmt.Sprintf("%d", level)}
chIds := []string{}
for _, dev := range devs {
ch, id, err := ada.getChannelId(dev)
if err != nil {
return "", errors.Wrapf(err, "get device %v channel id", dev)
}
chIds = append(chIds, ch, id)
}
args = append(args, chIds...)
cmd := ada.getCreateCmd(args...)
return cmd, nil
}
func (ada *AdaptecRaidAdaptor) buildRaid(level int, devs []*baremetal.BaremetalStorage, conf *api.BaremetalDiskConfig) error {
// initialize each devs
for _, dev := range devs {
channel, id, err := ada.getChannelId(dev)
if err != nil {
return errors.Wrapf(err, "get device %v channel id", dev)
}
if err := ada.initializeDevice(channel, id); err != nil {
// return errors.Wrapf(err, "initialize device")
log.Warningf("initialize device error: %v", err)
}
}
// TODO: support config to build raid params
cmd, err := ada.getBuildRaidCmd(level, devs)
if err != nil {
return err
}
_, err = ada.remoteRun(fmt.Sprintf("build raid %d", level), cmd)
return err
}
func (ada *AdaptecRaidAdaptor) BuildRaid0(devs []*baremetal.BaremetalStorage, conf *api.BaremetalDiskConfig) error {
return ada.buildRaid(0, devs, conf)
}
func (ada *AdaptecRaidAdaptor) BuildRaid1(devs []*baremetal.BaremetalStorage, conf *api.BaremetalDiskConfig) error {
return ada.buildRaid(1, devs, conf)
}
func (ada *AdaptecRaidAdaptor) BuildRaid5(devs []*baremetal.BaremetalStorage, conf *api.BaremetalDiskConfig) error {
return ada.buildRaid(5, devs, conf)
}
func (ada *AdaptecRaidAdaptor) BuildRaid10(devs []*baremetal.BaremetalStorage, conf *api.BaremetalDiskConfig) error {
return ada.buildRaid(10, devs, conf)
}
func (ada *AdaptecRaidAdaptor) GetDevices() []*baremetal.BaremetalStorage {
ret := []*baremetal.BaremetalStorage{}
for idx, dev := range ada.devs {
ret = append(ret, dev.ToBaremetalStorage(idx))
}
return ret
}
func (ada *AdaptecRaidAdaptor) GetLogicVolumes() ([]*raid.RaidLogicalVolume, error) {
cmd := GetCommand("GETCONFIG", fmt.Sprintf("%d", ada.index), "LD")
ret, err := ada.remoteRun("get logic volumes", cmd)
if err != nil {
return nil, fmt.Errorf("Get logic volumes error: %v", err)
}
return getLogicalVolumes(ada.index, ret)
}
func getLogicalVolumes(adapter int, lines []string) ([]*raid.RaidLogicalVolume, error) {
lvs := make([]*raid.RaidLogicalVolume, 0)
for _, line := range lines {
m := regutils2.SubGroupMatch(`Logical Device number\s+(?P<idx>\d+)`, line)
if len(m) > 0 {
idxStr := m["idx"]
idx, err := strconv.Atoi(idxStr)
if err != nil {
return nil, errors.Errorf("%s index str is not digit: %v", idxStr, err)
}
lvs = append(lvs, &raid.RaidLogicalVolume{
Index: idx,
Adapter: adapter,
})
}
}
return lvs, nil
}
func (ada *AdaptecRaidAdaptor) removeJBODDisks() error {
cmd := GetCommand("DELETE", fmt.Sprintf("%d", ada.index), "JBOD", "ALL", "noprompt")
out, err := ada.remoteRun("delete JBOD disks", cmd)
if err != nil {
return errors.Wrapf(err, "delete all JBOD output: %q", out)
}
return nil
}
func (ada *AdaptecRaidAdaptor) RemoveLogicVolumes() error {
lvs, err := ada.GetLogicVolumes()
if err != nil {
return errors.Wrap(err, "get logic volumes")
}
if len(lvs) == 0 {
return nil
}
cmd := GetCommand("DELETE", fmt.Sprintf("%d", ada.index), "LOGICALDRIVE", "ALL", "noprompt")
out, err := ada.remoteRun("delete logical volumes", cmd)
if err != nil {
return errors.Wrapf(err, "delete all logicaldrive output: %q", out)
}
return nil
}
type adaptorInfo struct {
status string
mode string
name string
sn string
wwn string
slot string
}
func (ada *adaptorInfo) key() string {
return ada.name + ada.sn
}
func (ada *adaptorInfo) isRaidExposeRawMode() bool {
return ada.mode == ControllerModeRaidExposeRaw
}
func (ada *adaptorInfo) isRaidHideRawMode() bool {
return ada.mode == ControllerModeRaidHideRaw
}
func (ada *adaptorInfo) isMixedMode() bool {
return ada.mode == ControllerModeMixed
}
func getAdaptorInfo(lines []string) (*adaptorInfo, error) {
ada := new(adaptorInfo)
for _, l := range lines {
key, val := stringutils.SplitKeyValue(l)
if len(key) == 0 {
continue
}
switch key {
case "Controller Status":
ada.status = val
case "Controller Mode":
ada.mode = val
case "Controller Model":
ada.name = val
case "Controller Serial Number":
ada.sn = val
case "Controller World Wide Name":
ada.wwn = val
case "Physical Slot":
ada.slot = val
}
}
if len(ada.key()) == 0 {
return nil, errors.Errorf("Not found SN and model name")
}
return ada, nil
}
type AdaptecRaidPhyDev struct {
*raid.RaidBasePhyDev
// channelId and deviceId is parsed by Reported Channel,Device(T:L)
// e.g.: Reported Channel,Device(T:L) : 0,6(6:0)
channelId string
deviceId string
}
func newPhyDev(adapter int) *AdaptecRaidPhyDev {
dev := &AdaptecRaidPhyDev{
RaidBasePhyDev: raid.NewRaidBasePhyDev(api.DISK_DRIVER_ADAPTECRAID),
}
dev.Adapter = adapter
return dev
}
func (dev *AdaptecRaidPhyDev) isComplete() bool {
if !dev.RaidBasePhyDev.IsComplete() {
return false
}
if dev.channelId == "" || dev.deviceId == "" {
return false
}
return true
}
func (dev *AdaptecRaidPhyDev) ToBaremetalStorage(index int) *baremetal.BaremetalStorage {
s := dev.RaidBasePhyDev.ToBaremetalStorage(index)
s.Addr = fmt.Sprintf("%s:%s", dev.channelId, dev.deviceId)
return s
}
var (
channelDeviceRegexp = regexp.MustCompile(`Reported Channel,Device\(T:L\).*(?P<channel>\d+),(?P<device>\d+)\(\d+:\d+\)`)
)
func (dev *AdaptecRaidPhyDev) parseLine(line string) bool {
chanDevMatch := regutils2.GetParams(channelDeviceRegexp, line)
if len(chanDevMatch) != 0 {
channelId := chanDevMatch["channel"]
deviceId := chanDevMatch["device"]
dev.channelId = channelId
dev.deviceId = deviceId
return true
}
key, val := stringutils.SplitKeyValue(line)
if key == "" {
return false
}
switch key {
case "Total Size":
dat := strings.Split(val, " ")
szStr, unitStr := dat[0], dat[1]
var sz int64
szInt, err := strconv.Atoi(szStr)
if err != nil {
log.Errorf("Parse size string %s: %v", szStr, err)
return false
}
switch unitStr {
case "GB":
sz = int64(szInt * 1000 * 1000 * 100)
case "TB":
sz = int64(szInt * 1000 * 1000 * 1000 * 1000)
case "MB":
sz = int64(szInt * 1000 * 1000)
default:
log.Errorf("Unsupported unit: %s", unitStr)
return false
}
dev.Size = sz / 1024 / 1024
case "Model":
dev.Model = strings.Join(regexp.MustCompile(`\s+`).Split(val, -1), " ")
case "State":
dev.Status = val
case "SSD":
if val == "No" {
dev.Rotate = tristate.True
} else {
dev.Rotate = tristate.False
}
default:
return false
}
return true
}