diff --git a/examples/install-firmware/doc.go b/examples/install-firmware/doc.go index b96767b9..2e7ae93c 100644 --- a/examples/install-firmware/doc.go +++ b/examples/install-firmware/doc.go @@ -2,23 +2,57 @@ install-firmware is an example commmand that utilizes the 'v1' bmclib interface methods to flash a firmware image to a BMC. - $ go run ./examples/v1/install-firmware/main.go -h - Usage of /tmp/go-build2950657412/b001/exe/main: - -cert-pool string - Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true - -firmware string - The local path of the firmware to install - -host string - BMC hostname to connect to - -password string - Username to login with - -port int - BMC port to connect to (default 443) - -secure-tls - Enable secure TLS - -user string - Username to login with - -version string - The firmware version being installed +Note: The example installs the firmware and polls until the status until the install is complete, +and if required by the install process - power cycles the host. + + $ go run ./examples/v1/install-firmware/main.go -h + Usage of /tmp/go-build2950657412/b001/exe/main: + -cert-pool string + Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true + -firmware string + The local path of the firmware to install + -host string + BMC hostname to connect to + -password string + Username to login with + -port int + BMC port to connect to (default 443) + -secure-tls + Enable secure TLS + -user string + Username to login with + -version string + The firmware version being installed + + # install bios firmware on a supermicro X11 + # + $ go run . -host 192.168.1.1 -user ADMIN -password hunter2 -component bios -firmware BIOS_X11DPH-0981_20220208_3.6_STD.bin + INFO[0007] set firmware install mode component=BIOS ip="https://192.168.1.1" model=X11DPH-T + INFO[0011] uploading firmware component=BIOS ip="https://192.168.1.1" model=X11DPH-T + INFO[0091] verifying uploaded firmware component=BIOS ip="https://192.168.1.1" model=X11DPH-T + INFO[0105] initiating firmware install component=BIOS ip="https://192.168.1.1" model=X11DPH-T + INFO[0115] firmware install running component=bios state=running + INFO[0132] firmware install running component=bios state=running + ... + ... + INFO[0628] firmware install running component=bios state=running + INFO[0635] host powercycle required component=bios state=powercycle-host + INFO[0637] host power cycled, all done! component=bios state=powercycle-host + + + + # install bmc firmware on a supermicro X11 + # + $ go run . -host 192.168.1.1 -user ADMIN -password hunter2 -component bmc -firmware BMC_X11AST2500-4101MS_20220225_01.74.02_STD.bin + INFO[0007] setting device to firmware install mode component=BMC ip="https://192.168.1.1" + INFO[0009] uploading firmware ip="https://192.168.1.1" + INFO[0045] verifying uploaded firmware ip="https://192.168.1.1" + INFO[0047] initiating firmware install ip="https://192.168.1.1" + INFO[0079] firmware install running component=bmc state=running + INFO[0085] firmware install running component=bmc state=running + ... + ... + INFO[0233] firmware install running component=bmc state=running + INFO[0238] firmware install completed component=bmc state=complete */ package main diff --git a/examples/install-firmware/main.go b/examples/install-firmware/main.go index 0a10d07d..57b88995 100644 --- a/examples/install-firmware/main.go +++ b/examples/install-firmware/main.go @@ -3,15 +3,17 @@ package main import ( "context" "crypto/x509" + "errors" "flag" "io/ioutil" "log" "os" + "strings" "time" bmclib "github.com/bmc-toolbox/bmclib/v2" "github.com/bmc-toolbox/bmclib/v2/constants" - "github.com/bmc-toolbox/common" + bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" "github.com/bombsimon/logrusr/v2" "github.com/sirupsen/logrus" ) @@ -20,24 +22,33 @@ 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") + //firmwareVersion := flag.String("version", "", "The firmware version being installed") flag.Parse() - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) defer cancel() l := logrus.New() - l.Level = logrus.DebugLevel + l.Level = logrus.TraceLevel logger := logrusr.New(l) if *host == "" || *user == "" || *pass == "" { l.Fatal("required host/user/pass parameters not defined") } - clientOpts := []bmclib.Option{bmclib.WithLogger(logger)} + + 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 @@ -53,7 +64,7 @@ func main() { clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool)) } - cl := bmclib.NewClient(*host, *user, *pass, clientOpts...) + cl := bmclib.NewClient(*host, *user, *pass, clientOpts...).PreferProvider("supermicro") err := cl.Open(ctx) if err != nil { l.Fatal(err, "bmc login failed") @@ -61,14 +72,6 @@ func main() { defer cl.Close(ctx) - // collect inventory - inventory, err := cl.Inventory(ctx) - if err != nil { - l.Fatal(err) - } - - l.WithField("bmc-version", inventory.BMC.Firmware.Installed).Info() - // open file handle fh, err := os.Open(*firmwarePath) if err != nil { @@ -76,17 +79,66 @@ func main() { } defer fh.Close() - // SlugBMC hardcoded here, this can be any of the existing component slugs from devices/constants.go - // assuming that the BMC provider implements the required component firmware update support - taskID, err := cl.FirmwareInstall(ctx, common.SlugBMC, constants.FirmwareApplyOnReset, true, fh) + _, err = cl.FirmwareInstall(ctx, *component, constants.FirmwareApplyOnReset, true, fh) if err != nil { - l.Error(err) + l.Fatal(err) } - state, err := cl.FirmwareInstallStatus(ctx, taskID, common.SlugBMC, *firmwareVersion) - if err != nil { - log.Fatal(err) - } + for { + if ctx.Err() != nil { + l.Fatal(ctx.Err()) + } + + state, err := cl.FirmwareInstallStatus(ctx, "", *component, "") + 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 + } - l.WithField("state", state).Info("BMC firmware install state") + 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: + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("firmware install running") + + case constants.FirmwareInstallFailed: + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("firmware install failed") + os.Exit(1) + + case constants.FirmwareInstallComplete: + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("firmware install completed") + os.Exit(0) + + case constants.FirmwareInstallPowerCyleHost: + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("host powercycle required") + + if _, err := cl.SetPowerState(ctx, "cycle"); err != nil { + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("error power cycling host for install") + os.Exit(1) + } + + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("host power cycled, all done!") + os.Exit(0) + default: + l.WithFields(logrus.Fields{"state": state, "component": *component}).Info("unknown state returned") + } + + time.Sleep(2 * time.Second) + } }