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

FS-1671; X13 Support #9

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
7 changes: 3 additions & 4 deletions bmc/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"

"github.com/metal-toolbox/bmclib/constants"
bconsts "github.com/metal-toolbox/bmclib/constants"
bmclibErrs "github.com/metal-toolbox/bmclib/errors"

"github.com/hashicorp/go-multierror"
Expand Down Expand Up @@ -481,7 +480,7 @@ type FirmwareTaskVerifier interface {
// return values:
// state - returns one of the FirmwareTask statuses (see devices/constants.go).
// status - returns firmware task progress or other arbitrary task information.
FirmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error)
FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error)
}

// firmwareTaskVerifierProvider is an internal struct to correlate an implementation/provider and its name
Expand All @@ -492,7 +491,7 @@ type firmwareTaskVerifierProvider struct {

// firmwareTaskStatus returns the status of the firmware upload process.

func firmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string, generic []firmwareTaskVerifierProvider) (state constants.TaskState, status string, metadata Metadata, err error) {
func firmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string, generic []firmwareTaskVerifierProvider) (state constants.TaskState, status string, metadata Metadata, err error) {
metadata = newMetadata()

for _, elem := range generic {
Expand Down Expand Up @@ -522,7 +521,7 @@ func firmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, c
}

// FirmwareTaskStatusFromInterfaces identifies implementations of the FirmwareTaskVerifier interface and passes the found implementations to the firmwareTaskStatus() wrapper.
func FirmwareTaskStatusFromInterfaces(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string, generic []interface{}) (state constants.TaskState, status string, metadata Metadata, err error) {
func FirmwareTaskStatusFromInterfaces(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string, generic []interface{}) (state constants.TaskState, status string, metadata Metadata, err error) {
metadata = newMetadata()

implementations := make([]firmwareTaskVerifierProvider, 0)
Expand Down
14 changes: 6 additions & 8 deletions examples/reset_bmc/reset_bmc.go → examples/reset_bmc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"flag"
"log"
"os"
"time"
Expand All @@ -16,22 +17,19 @@ func main() {
defer cancel()

// set BMC parameters here
host := "10.211.132.157"
user := "root"
pass := "yxvZdxAQ38ZWlZ"
host := flag.String("host", "", "BMC hostname to connect to")
user := flag.String("user", "", "Username to login with")
pass := flag.String("pass", "", "Username to login with")
flag.Parse()

l := logrus.New()
l.Level = logrus.DebugLevel
logger := logrusr.New(l)

if host == "" || user == "" || pass == "" {
log.Fatal("required host/user/pass parameters not defined")
}

os.Setenv("DEBUG_BMCLIB", "true")
defer os.Unsetenv("DEBUG_BMCLIB")

cl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(logger))
cl := bmclib.NewClient(*host, *user, *pass, bmclib.WithLogger(logger))

err := cl.Open(ctx)
if err != nil {
Expand Down
216 changes: 216 additions & 0 deletions examples/upload-install-firmware/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package main

import (
"context"
"crypto/x509"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
"time"

"github.com/bombsimon/logrusr/v2"
bmclib "github.com/metal-toolbox/bmclib"
"github.com/metal-toolbox/bmclib/constants"
bmclibErrs "github.com/metal-toolbox/bmclib/errors"
"github.com/sirupsen/logrus"
)

func main() {
user := flag.String("user", "", "Username to login with")
pass := flag.String("password", "", "Username to login with")
host := flag.String("host", "", "BMC hostname to connect to")
component := flag.String("component", "", "Component to be updated (bmc, bios.. etc)")
withSecureTLS := flag.Bool("secure-tls", false, "Enable secure TLS")
certPoolPath := flag.String("cert-pool", "", "Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true")
firmwarePath := flag.String("firmware", "", "The local path of the firmware to install")
firmwareVersion := flag.String("version", "", "The firmware version being installed")

flag.Parse()

ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute)
defer cancel()

l := logrus.New()
l.Level = logrus.TraceLevel
logger := logrusr.New(l)

if *host == "" || *user == "" || *pass == "" {
l.Fatal("required host/user/pass parameters not defined")
}

if *component == "" {
l.Fatal("component parameter required (must be a component slug - bmc, bios etc)")
}

clientOpts := []bmclib.Option{
bmclib.WithLogger(logger),
bmclib.WithPerProviderTimeout(time.Minute * 30),
}

if *withSecureTLS {
var pool *x509.CertPool
if *certPoolPath != "" {
pool = x509.NewCertPool()
data, err := ioutil.ReadFile(*certPoolPath)
if err != nil {
l.Fatal(err)
}
pool.AppendCertsFromPEM(data)
}
// a nil pool uses the system certs
clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))
}

cl := bmclib.NewClient(*host, *user, *pass, clientOpts...)

err := cl.Open(ctx)
if err != nil {
l.Fatal(err, "bmc login failed")
}

defer cl.Close(ctx)

// open file handle
fh, err := os.Open(*firmwarePath)
if err != nil {
l.Fatal(err)
}
defer fh.Close()

steps, err := cl.FirmwareInstallSteps(ctx, *component)
if err != nil {
l.Fatal(err)
}

sprinted := fmt.Sprintf("%v", steps)
trimmed := strings.Trim(sprinted, "[]")
replaced := strings.Replace(trimmed, " ", " - ", 0)

l.Infof("Steps: %s", replaced)

taskID := ""
var lastStep constants.FirmwareInstallStep = ""
for _, step := range steps {
l.Infof("Step: %s", step)

switch step {
case constants.FirmwareInstallStepUploadInitiateInstall:
taskID, err = cl.FirmwareInstallUploadAndInitiate(ctx, *component, fh)
if err != nil {
l.Fatal(err)
}
// X11 doesnt have a taskID, so lets give it a dummy one
if taskID == "" {
taskID = "0"
}
case constants.FirmwareInstallStepInstallStatus:
fallthrough
case constants.FirmwareInstallStepUploadStatus:
if taskID == "" {
l.Warn("taskID wasnt set, continueing anyway")
}
if lastStep == "" {
l.Fatal("lastStep wasnt set")
}
firmwareInstallStatusWait(ctx, cl, l, lastStep, *component, *firmwareVersion, taskID)
case constants.FirmwareInstallStepUpload:
taskID, err = cl.FirmwareUpload(ctx, *component, fh)
if err != nil {
l.Fatal(err)
}
// X11 doesnt have a taskID, so lets give it a dummy one
if taskID == "" {
taskID = "0"
}
case constants.FirmwareInstallStepInstallUploaded:
if taskID == "" {
l.Fatal("taskID wasnt set")
}
taskID, err = cl.FirmwareInstallUploaded(ctx, *component, taskID)
if err != nil {
l.Fatal(err)
}
// X11 doesnt have a taskID, so lets give it a dummy one
if taskID == "" {
taskID = "0"
}
case constants.FirmwareInstallStepPowerOffHost:
_, err = cl.SetPowerState(ctx, "off")
if err != nil {
l.Fatal(err)
}
case constants.FirmwareInstallStepResetBMCPostInstall:
fallthrough
case constants.FirmwareInstallStepResetBMCOnInstallFailure:
_, err = cl.ResetBMC(ctx, "GracefulRestart")
if err != nil {
l.Fatal(err)
}
default:
l.Fatal("unknown firmware install step")
}

lastStep = step
}
}

func firmwareInstallStatusWait(ctx context.Context, cl *bmclib.Client, l *logrus.Logger, step constants.FirmwareInstallStep, component, firmwareVersion, taskID string) {
for range 300 {
if ctx.Err() != nil {
l.Fatal(ctx.Err())
}

state, status, err := cl.FirmwareTaskStatus(ctx, step, component, taskID, firmwareVersion)
if err != nil {
// when its under update a connection refused is returned
if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "operation timed out") {
l.Info("BMC refused connection, BMC most likely resetting...")
time.Sleep(2 * time.Second)

continue
}

if errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), "session expired") {
err := cl.Open(ctx)
if err != nil {
l.Fatal(err, "bmc re-login failed")
}

l.WithFields(logrus.Fields{"state": state, "component": component}).Info("BMC session expired, logging in...")

continue
}

log.Fatal(err)
}

switch state {
case constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing:
l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Infof("%s running", step)
case constants.FirmwareInstallFailed:
l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Infof("%s failed", step)
os.Exit(1)
case constants.FirmwareInstallComplete, constants.FirmwareInstallQueued:
l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Infof("%s completed", step)
return
case constants.FirmwareInstallPowerCycleHost:
l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Info("host powercycle required")

if _, err := cl.SetPowerState(ctx, "cycle"); err != nil {
l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Infof("error power cycling host for %s", step)
os.Exit(1)
}

l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Info("host power cycled, all done!")
return
default:
l.WithFields(logrus.Fields{"state": state, "status": status, "component": component}).Info("unknown state returned")
}

time.Sleep(2 * time.Second)
}
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/metal-toolbox/bmclib

go 1.21
go 1.22

require (
dario.cat/mergo v1.0.0
Expand All @@ -13,6 +13,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef
github.com/jacobweinstock/registrar v0.4.7
github.com/metal-toolbox/bmc-common v1.0.2
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.31.0
github.com/sirupsen/logrus v1.9.3
Expand All @@ -34,7 +35,6 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/metal-toolbox/bmc-common v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 h1:t95Grn2
github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230/go.mod h1:t2EzW1qybnPDQ3LR/GgeF0GOzHUXT5IVMLP2gkW1cmc=
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 h1:a0MBqYm44o0NcthLKCljZHe1mxlN6oahCQHHThnSwB4=
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22/go.mod h1:/B7V22rcz4860iDqstGvia/2+IYWXf3/JdQCVd/1D2A=
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 h1:/BjZSX/sphptIdxpYo4wxAQkgMLyMMgfdl48J9DKNeE=
github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=
github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM=
github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
Expand Down
15 changes: 15 additions & 0 deletions internal/redfishwrapper/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@ func (c *Client) StartUpdateForUploadedFirmware(ctx context.Context) (taskID str
return taskIDFromResponseBody(response)
}

// StartUpdateForUploadedFirmware starts an update for a firmware file previously uploaded
func (c *Client) StartUpdateForUploadedFirmwareNoTaskID(ctx context.Context) error {
updateService, err := c.client.Service.UpdateService()
if err != nil {
return errors.Wrap(err, "error querying redfish update service")
}

err = updateService.StartUpdate()
if err != nil {
return errors.Wrap(err, "error querying redfish start update endpoint")
}

return nil
}

type TaskAccepted struct {
Accepted struct {
Code string `json:"code"`
Expand Down
2 changes: 2 additions & 0 deletions providers/supermicro/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var (
ErrXMLAPIUnsupported = errors.New("XML API is unsupported")
ErrModelUnknown = errors.New("Model number unknown")
ErrModelUnsupported = errors.New("Model not supported")

ErrUploadTaskIDEmpty = errors.New("firmware upload request returned empty firmware upload verify TaskID")
)

type UnexpectedResponseError struct {
Expand Down
3 changes: 2 additions & 1 deletion providers/supermicro/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
"X11SSE-F",
"X12STH-SYS",
"X12SPO-NTF",
"X13DEM",
}

errUploadTaskIDExpected = errors.New("expected an firmware upload taskID")
Expand All @@ -46,7 +47,7 @@ func (c *Client) FirmwareUpload(ctx context.Context, component string, file *os.
return "", err
}

// // expect atleast 5 minutes left in the deadline to proceed with the upload
// expect atleast 5 minutes left in the deadline to proceed with the upload
d, _ := ctx.Deadline()
if time.Until(d) < 5*time.Minute {
return "", errors.New("remaining context deadline insufficient to perform update: " + time.Until(d).String())
Expand Down
Loading
Loading