-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #377 from bmc-toolbox/openbmc
Add OpenBMC support
- Loading branch information
Showing
3 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package openbmc | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/bmc-toolbox/bmclib/v2/constants" | ||
"github.com/bmc-toolbox/common" | ||
|
||
bmcliberrs "github.com/bmc-toolbox/bmclib/v2/errors" | ||
rfw "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" | ||
"github.com/pkg/errors" | ||
"github.com/stmcginnis/gofish/redfish" | ||
) | ||
|
||
// bmc client interface implementations methods | ||
func (c *Conn) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) { | ||
if err := c.deviceSupported(ctx); err != nil { | ||
return nil, err | ||
} | ||
|
||
switch strings.ToUpper(component) { | ||
case common.SlugBIOS: | ||
return []constants.FirmwareInstallStep{ | ||
constants.FirmwareInstallStepPowerOffHost, | ||
constants.FirmwareInstallStepUploadInitiateInstall, | ||
constants.FirmwareInstallStepInstallStatus, | ||
}, nil | ||
case common.SlugBMC: | ||
return []constants.FirmwareInstallStep{ | ||
constants.FirmwareInstallStepUploadInitiateInstall, | ||
constants.FirmwareInstallStepInstallStatus, | ||
}, nil | ||
default: | ||
return nil, errors.New("component firmware install not supported: " + component) | ||
} | ||
} | ||
|
||
func (c *Conn) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) { | ||
if err := c.deviceSupported(ctx); err != nil { | ||
return "", errNotOpenBMCDevice | ||
} | ||
|
||
// // expect atleast 5 minutes left in the deadline to proceed with the upload | ||
d, _ := ctx.Deadline() | ||
if time.Until(d) < 10*time.Minute { | ||
return "", errors.New("remaining context deadline insufficient to perform update: " + time.Until(d).String()) | ||
} | ||
|
||
// list current tasks on BMC | ||
tasks, err := c.redfishwrapper.Tasks(ctx) | ||
if err != nil { | ||
return "", errors.Wrap(err, "error listing bmc redfish tasks") | ||
} | ||
|
||
// validate a new firmware install task can be queued | ||
if err := c.checkQueueability(component, tasks); err != nil { | ||
return "", errors.Wrap(bmcliberrs.ErrFirmwareInstall, err.Error()) | ||
} | ||
|
||
params := &rfw.RedfishUpdateServiceParameters{ | ||
Targets: []string{}, | ||
OperationApplyTime: constants.OnReset, | ||
Oem: []byte(`{}`), | ||
} | ||
|
||
return c.redfishwrapper.FirmwareUpload(ctx, file, params) | ||
} | ||
|
||
// returns an error when a bmc firmware install is active | ||
func (c *Conn) checkQueueability(component string, tasks []*redfish.Task) error { | ||
errTaskActive := errors.New("A firmware job was found active for component: " + component) | ||
|
||
for _, t := range tasks { | ||
// taskInfo returned in error if any. | ||
taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", t.ID, t.TaskState, t.TaskStatus) | ||
|
||
// convert redfish task state to bmclib state | ||
convstate := c.redfishwrapper.ConvertTaskState(string(t.TaskState)) | ||
// check if task is active based on converted state | ||
active, err := c.redfishwrapper.TaskStateActive(convstate) | ||
if err != nil { | ||
return errors.Wrap(err, taskInfo) | ||
} | ||
|
||
if active { | ||
return errors.Wrap(errTaskActive, taskInfo) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// FirmwareTaskStatus returns the status of a firmware related task queued on the BMC. | ||
func (c *Conn) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) { | ||
return c.redfishwrapper.TaskStatus(ctx, taskID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package openbmc | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto/x509" | ||
"io" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/bmc-toolbox/bmclib/v2/internal/httpclient" | ||
"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" | ||
"github.com/bmc-toolbox/bmclib/v2/providers" | ||
"github.com/bmc-toolbox/common" | ||
"github.com/go-logr/logr" | ||
"github.com/jacobweinstock/registrar" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
const ( | ||
// ProviderName for the OpenBMC provider implementation | ||
ProviderName = "openbmc" | ||
// ProviderProtocol for the OpenBMC provider implementation | ||
ProviderProtocol = "redfish" | ||
) | ||
|
||
var ( | ||
// Features implemented by dell redfish | ||
Features = registrar.Features{ | ||
providers.FeaturePowerState, | ||
providers.FeaturePowerSet, | ||
providers.FeatureFirmwareInstallSteps, | ||
providers.FeatureFirmwareUploadInitiateInstall, | ||
providers.FeatureFirmwareTaskStatus, | ||
providers.FeatureInventoryRead, | ||
} | ||
|
||
errNotOpenBMCDevice = errors.New("not an OpenBMC device") | ||
) | ||
|
||
type Config struct { | ||
HttpClient *http.Client | ||
Port string | ||
VersionsNotCompatible []string | ||
RootCAs *x509.CertPool | ||
UseBasicAuth bool | ||
} | ||
|
||
// Option for setting optional Client values | ||
type Option func(*Config) | ||
|
||
func WithHttpClient(httpClient *http.Client) Option { | ||
return func(c *Config) { | ||
c.HttpClient = httpClient | ||
} | ||
} | ||
|
||
func WithPort(port string) Option { | ||
return func(c *Config) { | ||
c.Port = port | ||
} | ||
} | ||
|
||
func WithRootCAs(rootCAs *x509.CertPool) Option { | ||
return func(c *Config) { | ||
c.RootCAs = rootCAs | ||
} | ||
} | ||
|
||
func WithUseBasicAuth(useBasicAuth bool) Option { | ||
return func(c *Config) { | ||
c.UseBasicAuth = useBasicAuth | ||
} | ||
} | ||
|
||
// Conn details for redfish client | ||
type Conn struct { | ||
host string | ||
httpClient *http.Client | ||
redfishwrapper *redfishwrapper.Client | ||
Log logr.Logger | ||
} | ||
|
||
// New returns connection with a redfish client initialized | ||
func New(host, user, pass string, log logr.Logger, opts ...Option) *Conn { | ||
defaultConfig := &Config{ | ||
HttpClient: httpclient.Build(), | ||
Port: "443", | ||
VersionsNotCompatible: []string{}, | ||
} | ||
|
||
for _, opt := range opts { | ||
opt(defaultConfig) | ||
} | ||
|
||
rfOpts := []redfishwrapper.Option{ | ||
redfishwrapper.WithHTTPClient(defaultConfig.HttpClient), | ||
redfishwrapper.WithBasicAuthEnabled(defaultConfig.UseBasicAuth), | ||
redfishwrapper.WithEtagMatchDisabled(true), | ||
} | ||
|
||
if defaultConfig.RootCAs != nil { | ||
rfOpts = append(rfOpts, redfishwrapper.WithSecureTLS(defaultConfig.RootCAs)) | ||
} | ||
|
||
return &Conn{ | ||
host: host, | ||
httpClient: defaultConfig.HttpClient, | ||
Log: log, | ||
redfishwrapper: redfishwrapper.NewClient(host, defaultConfig.Port, user, pass, rfOpts...), | ||
} | ||
} | ||
|
||
// Open a connection to a BMC via redfish | ||
func (c *Conn) Open(ctx context.Context) (err error) { | ||
if err := c.deviceSupported(ctx); err != nil { | ||
return nil | ||
} | ||
|
||
if err := c.redfishwrapper.Open(ctx); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (c *Conn) deviceSupported(ctx context.Context) error { | ||
var host = c.host | ||
if !strings.HasPrefix(host, "https://") && !strings.HasPrefix(host, "http://") { | ||
host = "https://" + host | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, host, nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
resp, err := c.httpClient.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer resp.Body.Close() | ||
|
||
b, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !bytes.Contains(b, []byte(`OpenBMC`)) { | ||
return errNotOpenBMCDevice | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Close a connection to a BMC via redfish | ||
func (c *Conn) Close(ctx context.Context) error { | ||
return c.redfishwrapper.Close(ctx) | ||
} | ||
|
||
// Name returns the client provider name. | ||
func (c *Conn) Name() string { | ||
return ProviderName | ||
} | ||
|
||
// PowerStateGet gets the power state of a BMC machine | ||
func (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) { | ||
return c.redfishwrapper.SystemPowerStatus(ctx) | ||
} | ||
|
||
// PowerSet sets the power state of a server | ||
func (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) { | ||
return c.redfishwrapper.PowerSet(ctx, state) | ||
} | ||
|
||
// Inventory collects hardware inventory and install firmware information | ||
func (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) { | ||
return c.redfishwrapper.Inventory(ctx, false) | ||
} |