Skip to content
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

Add BMC and machine information #61

Merged
merged 5 commits into from
May 6, 2024
Merged
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
49 changes: 29 additions & 20 deletions internal/bmc/bmc.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,37 @@ type Credentials struct {
}

type Info struct {
Type Typ
Manufacturer string
SerialNumber string
FirmwareVersion string
Console string
Machines []Machine
}

type Typ string

const (
TypeMachine Typ = "Machine"
TypeSwitch Typ = "Switch"
TypeRouter Typ = "Router"
)

type Machine struct {
UUID string
Type string
Capabilities []string
SerialNumber string
SKU string
Manufacturer string
LocatorLED LED
SKU string
SerialNumber string
Power Power
OS string
OSReason string
Console string
FWVersion string
LocatorLED LED
}

type Typ string
type Power string

type LEDControl interface {
SetLocatorLED(ctx context.Context, state LED) (LED, error)
}
const (
PowerOn Power = "On"
PowerOff Power = "Off"
)

type LED string

Expand All @@ -55,18 +67,15 @@ const (
LEDBlinking LED = "Blinking"
)

type LEDControl interface {
SetLocatorLED(ctx context.Context, state LED) (LED, error)
}

type PowerControl interface {
PowerOn(ctx context.Context) error
PowerOff(ctx context.Context, force bool) error
}

type Power string

const (
PowerOn Power = "On"
PowerOff Power = "Off"
)

type RestartControl interface {
Restart(ctx context.Context, force bool) error
}
Expand Down
24 changes: 23 additions & 1 deletion internal/bmc/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"regexp"
"time"

"github.com/google/uuid"
)

func RegisterFake() {
Expand Down Expand Up @@ -81,7 +83,27 @@ func (b *FakeBMC) DeleteUsers(_ context.Context, _ *regexp.Regexp) error {
}

func (b *FakeBMC) ReadInfo(_ context.Context) (Info, error) {
return Info{}, nil
id, err := uuid.NewRandom()
if err != nil {
return Info{}, fmt.Errorf("cannot generate UUID: %w", err)
}

return Info{
Type: TypeMachine,
Manufacturer: "Fake",
SerialNumber: "0",
FirmwareVersion: "1",
Machines: []Machine{
{
UUID: id.String(),
Manufacturer: "Fake",
SKU: "Fake-0",
SerialNumber: "1",
Power: PowerOn,
LocatorLED: LEDOff,
},
},
}, nil
}

func (b *FakeBMC) SetLocatorLED(_ context.Context, state LED) (LED, error) {
Expand Down
88 changes: 16 additions & 72 deletions internal/bmc/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (
"time"

"github.com/hashicorp/go-multierror"
"golang.org/x/text/cases"
"golang.org/x/text/language"

"github.com/ironcore-dev/metal/internal/log"
)
Expand Down Expand Up @@ -71,20 +69,20 @@ type IPMIUser struct {

// TODO: ipmi ciphers, should this also be tested?

func outputmap(ml string, mi *map[string]string) {
for _, line := range strings.Split(ml, "\n") {
colon := strings.Index(line, ":")
if colon == -1 {
continue
}
k := strings.TrimSpace(line[:colon])
v := strings.TrimSpace(line[colon+1:])
if k == "" {
continue
}
(*mi)[k] = v
}
}
//func outputmap(ml string, mi *map[string]string) {
// for _, line := range strings.Split(ml, "\n") {
// colon := strings.Index(line, ":")
// if colon == -1 {
// continue
// }
// k := strings.TrimSpace(line[:colon])
// v := strings.TrimSpace(line[colon+1:])
// if k == "" {
// continue
// }
// (*mi)[k] = v
// }
//}

func outputmapspace(ml string, mi *map[string]string) {
for _, line := range strings.Split(ml, "\n") {
Expand Down Expand Up @@ -146,62 +144,8 @@ func (b *IPMIBMC) Connect(ctx context.Context) error {

func (b *IPMIBMC) ReadInfo(ctx context.Context) (Info, error) {
log.Debug(ctx, "Reading BMC info", "host", b.host)
info := make(map[string]string)

out, serr, err := ipmiExecuteCommand(ctx, b.host, b.port, b.creds, "ipmitool", "fru", "list", "0")
if err != nil {
return Info{}, fmt.Errorf("cannot get fru info, stderr: %s: %w", serr, err)
}
outputmap(out, &info)

out, serr, err = ipmiExecuteCommand(ctx, b.host, b.port, b.creds, "ipmitool", "mc", "guid")
if err != nil {
return Info{}, fmt.Errorf("cannot get mc info, stderr: %s: %w", serr, err)
}
outputmap(out, &info)

out, serr, err = ipmiExecuteCommand(ctx, b.host, b.port, b.creds, "ipmi-chassis", "--get-chassis-status")
if err != nil {
return Info{}, fmt.Errorf("cannot get chassis status, stderr: %s: %w", serr, err)
}
outputmap(out, &info)

out, serr, err = ipmiExecuteCommand(ctx, b.host, b.port, b.creds, "ipmitool", "bmc", "info")
if err != nil {
return Info{}, fmt.Errorf("cannot get bmc info, stderr: %s: %w", serr, err)
}
outputmap(out, &info)

uuid, ok := info["System GUID"]
if !ok {
return Info{}, fmt.Errorf("cannot determine uuid for machine")
}
uuid = strings.ToLower(uuid)
powerstate, ok := info["System Power"]
if !ok {
return Info{}, fmt.Errorf("cannot determine the power state for machine")
}
serial := info["Product Serial"]
sku := info["Product SKU"]
manufacturer := info["Manufacturer"]
//TODO: currently we can't handle this correctly as we can't read the state on most hardware
//led, ok := info["Chassis Identify State"]
led := ""
fw := info["Firmware Revision"]

//TODO: properly detect if sol is supported
return Info{
UUID: uuid,
Type: "BMC",
Capabilities: []string{"credentials", "power", "led", "console"},
SerialNumber: serial,
SKU: sku,
Manufacturer: manufacturer,
LocatorLED: LED(led),
Power: Power(cases.Title(language.English).String(powerstate)),
Console: "ipmi",
FWVersion: fw,
}, nil
// TODO: implement proper ipmi readinfo
return Info{}, nil
}

func ipmiGenerateCommand(ctx context.Context, host string, port int32, creds Credentials, cmd ...string) ([]string, error) {
Expand Down
88 changes: 19 additions & 69 deletions internal/bmc/redfish.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,61 +601,26 @@ func (b *RedfishBMC) ReadInfo(ctx context.Context) (Info, error) {
defer c.Logout()

log.Debug(ctx, "Reading BMC info")
info := Info{}
manufacturer := ""
systems, err := c.Service.Systems()
if err != nil {
return Info{}, fmt.Errorf("cannot get systems information: %w", err)
}
if len(systems) == 0 {
return Info{}, fmt.Errorf("cannot get systems information")
}

uuid := systems[0].UUID
if uuid == "" {
return Info{}, fmt.Errorf("BMC has no UUID attribute")
}
uuid = strings.ToLower(uuid)

led := LED(systems[0].IndicatorLED)

// Reading the OS state is supported only on Lenovo hardware
var os, osReason string
if len(systems) > 0 {
sys := systems[0]
sysRaw, err := c.Get(sys.ODataID)
if err != nil {
return Info{}, fmt.Errorf("cannot get systems (raw): %w", err)
}
osStatus := struct {
OEM struct {
Lenovo struct {
SystemStatus *string `json:"SystemStatus"`
} `json:"Lenovo"`
} `json:"OEM"`
}{}

decoder := json.NewDecoder(sysRaw.Body)
err = decoder.Decode(&osStatus)
if err != nil {
return Info{}, fmt.Errorf("cannot decode information for OS status: %w", err)
}

if osStatus.OEM.Lenovo.SystemStatus != nil {
if sys.PowerState == redfish.OffPowerState {
osReason = "PoweredOff"
} else if state := *osStatus.OEM.Lenovo.SystemStatus; state == "OSBooted" || state == "BootingOSOrInUndetectedOS" {
os = "Ok"
osReason = state
} else {
osReason = state
}
for _, system := range systems {
machine := Machine{}
manufacturer = system.Manufacturer
machine.Manufacturer = manufacturer
machine.Power = Power(fmt.Sprintf("%v", system.PowerState))
machine.SKU = system.SKU
machine.LocatorLED = LED(system.IndicatorLED)
machine.SerialNumber = system.SerialNumber
machine.UUID = strings.ToLower(system.UUID)
if machine.UUID == "" {
return Info{}, fmt.Errorf("system has no UUID")
}
info.Machines = append(info.Machines, machine)
}

manufacturer := systems[0].Manufacturer
capabilities := []string{"credentials", "power", "led"}
console := ""
fw := ""

mgr, err := c.Service.Managers()
if err != nil {
return Info{}, fmt.Errorf("cannot get managers: %w", err)
Expand All @@ -664,30 +629,15 @@ func (b *RedfishBMC) ReadInfo(ctx context.Context) (Info, error) {
consoleList := mgr[0].SerialConsole.ConnectTypesSupported
if mgr[0].SerialConsole.ServiceEnabled {
if strings.ToLower(manufacturer) == "lenovo" && isConsoleTypeSupported(consoleList, redfish.SSHSerialConnectTypesSupported) {
capabilities = append(capabilities, "console")
console = "ssh-lenovo"
info.Console = "ssh-lenovo"
} else if isConsoleTypeSupported(consoleList, redfish.IPMISerialConnectTypesSupported) {
capabilities = append(capabilities, "console")
console = "ipmi"
info.Console = "ipmi"
}
fw = mgr[0].FirmwareVersion
info.FirmwareVersion = mgr[0].FirmwareVersion
}
}

return Info{
UUID: uuid,
Type: "BMC",
Capabilities: capabilities,
SerialNumber: systems[0].SerialNumber,
SKU: systems[0].SKU,
Manufacturer: manufacturer,
LocatorLED: led,
Power: Power(fmt.Sprintf("%v", systems[0].PowerState)),
OS: os,
OSReason: osReason,
Console: console,
FWVersion: fw,
}, nil
info.Manufacturer = manufacturer
return info, nil
}

func isConsoleTypeSupported(consoleList []redfish.SerialConnectTypesSupported, console redfish.SerialConnectTypesSupported) bool {
Expand Down
Loading