diff --git a/providers/supermicro/errors.go b/providers/supermicro/errors.go index 49161b28..c2dcf057 100644 --- a/providers/supermicro/errors.go +++ b/providers/supermicro/errors.go @@ -11,6 +11,7 @@ var ( ErrQueryFRUInfo = errors.New("FRU information query returned error") ErrXMLAPIUnsupported = errors.New("XML API is unsupported") ErrModelUnknown = errors.New("Model number unknown") + ErrModelUnsupported = errors.New("Model not supported") ) type UnexpectedResponseError struct { diff --git a/providers/supermicro/firmware.go b/providers/supermicro/firmware.go index 42804a3e..68329082 100644 --- a/providers/supermicro/firmware.go +++ b/providers/supermicro/firmware.go @@ -33,24 +33,16 @@ var ( ) // bmc client interface implementations methods - func (c *Client) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) { - if err := c.firmwareInstallSupported(ctx); err != nil { + if err := c.serviceClient.supportsFirmwareInstall(ctx, c.bmc.deviceModel()); err != nil { return nil, err } - switch { - case strings.HasPrefix(strings.ToLower(c.model), "x12"): - return c.x12().firmwareInstallSteps(component) - case strings.HasPrefix(strings.ToLower(c.model), "x11"): - return c.x11().firmwareInstallSteps(component) - } - - return nil, errors.Wrap(errUnexpectedModel, c.model) + return c.bmc.firmwareInstallSteps(component) } func (c *Client) FirmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) { - if err := c.firmwareInstallSupported(ctx); err != nil { + if err := c.serviceClient.supportsFirmwareInstall(ctx, c.bmc.deviceModel()); err != nil { return "", err } @@ -60,62 +52,28 @@ func (c *Client) FirmwareUpload(ctx context.Context, component string, file *os. return "", errors.New("remaining context deadline insufficient to perform update: " + time.Until(d).String()) } - switch { - case strings.HasPrefix(strings.ToLower(c.model), "x12"): - return c.x12().firmwareUpload(ctx, component, file) - case strings.HasPrefix(strings.ToLower(c.model), "x11"): - return c.x11().firmwareUpload(ctx, component, file) - } - - return "", errors.Wrap(errUnexpectedModel, c.model) + return c.bmc.firmwareUpload(ctx, component, file) } func (c *Client) FirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) { - if err := c.firmwareInstallSupported(ctx); err != nil { + if err := c.serviceClient.supportsFirmwareInstall(ctx, c.bmc.deviceModel()); err != nil { return "", err } - if uploadTaskID == "" { + // x11's don't return a upload Task ID, since the upload mechanism is not redfish + if !strings.HasPrefix(c.bmc.deviceModel(), "x11") && uploadTaskID == "" { return "", errUploadTaskIDExpected } - switch { - case strings.HasPrefix(strings.ToLower(c.model), "x12"): - return c.x12().firmwareInstallUploaded(ctx, component, uploadTaskID) - case strings.HasPrefix(strings.ToLower(c.model), "x11"): - return "", c.x11().firmwareInstallUploaded(ctx, component) - } - - return "", errors.Wrap(errUnexpectedModel, c.model) - + return c.bmc.firmwareInstallUploaded(ctx, component, uploadTaskID) } // FirmwareTaskStatus returns the status of a firmware related task queued on the BMC. func (c *Client) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state, status string, err error) { - if err := c.firmwareInstallSupported(ctx); err != nil { + if err := c.serviceClient.supportsFirmwareInstall(ctx, c.bmc.deviceModel()); err != nil { return "", "", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error()) } component = strings.ToUpper(component) - - if strings.HasPrefix(strings.ToLower(c.model), "x12") { - return c.x12().firmwareTaskStatus(ctx, component, taskID) - } else if strings.HasPrefix(strings.ToLower(c.model), "x11") { - return c.x11().firmwareTaskStatus(ctx, component) - - } - - return "", "", errors.Wrap(errUnexpectedModel, c.model) -} - -func (c *Client) firmwareInstallSupported(ctx context.Context) error { - errBoardUnsupported := errors.New("firmware install not supported/implemented for device model") - - for _, s := range supportedModels { - if strings.EqualFold(s, c.model) { - return nil - } - } - - return errBoardUnsupported + return c.bmc.firmwareTaskStatus(ctx, component, taskID) } diff --git a/providers/supermicro/firmware_bios_test.go b/providers/supermicro/firmware_bios_test.go index 7e6b117e..f4bdfb53 100644 --- a/providers/supermicro/firmware_bios_test.go +++ b/providers/supermicro/firmware_bios_test.go @@ -8,6 +8,7 @@ import ( "net/url" "testing" + "github.com/bmc-toolbox/bmclib/v2/internal/httpclient" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" ) @@ -79,8 +80,11 @@ func Test_setComponentUpdateMisc(t *testing.T) { t.Fatal(err) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - if err := client.x11().checkComponentUpdateMisc(context.Background(), tc.stage); err != nil { + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + serviceClient.csrfToken = "foobar" + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + if err := client.checkComponentUpdateMisc(context.Background(), tc.stage); err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) @@ -165,8 +169,11 @@ func Test_setBIOSFirmwareInstallMode(t *testing.T) { t.Fatal(err) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - if err := client.x11().setBMCFirmwareInstallMode(context.Background()); err != nil { + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + serviceClient.csrfToken = "foobar" + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + if err := client.setBMCFirmwareInstallMode(context.Background()); err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) diff --git a/providers/supermicro/floppy.go b/providers/supermicro/floppy.go index 3cd3dceb..a1967fdf 100644 --- a/providers/supermicro/floppy.go +++ b/providers/supermicro/floppy.go @@ -24,7 +24,7 @@ func (c *Client) floppyImageMounted(ctx context.Context) (bool, error) { return false, err } - inserted, err := c.redfish.InsertedVirtualMedia(ctx) + inserted, err := c.serviceClient.redfish.InsertedVirtualMedia(ctx) if err != nil { return false, err } @@ -60,7 +60,7 @@ func (c *Client) MountFloppyImage(ctx context.Context, image io.Reader) error { }, { name: "csrf-token", - data: bytes.NewBufferString(c.csrfToken), + data: bytes.NewBufferString(c.serviceClient.csrfToken), }, } @@ -103,7 +103,7 @@ func (c *Client) MountFloppyImage(ctx context.Context, image io.Reader) error { } payloadWriter.Close() - resp, statusCode, err := c.query( + resp, statusCode, err := c.serviceClient.query( ctx, "cgi/uimapin.cgi", http.MethodPost, @@ -133,7 +133,7 @@ func (c *Client) UnmountFloppyImage(ctx context.Context) error { return nil } - resp, statusCode, err := c.query( + resp, statusCode, err := c.serviceClient.query( ctx, "cgi/uimapout.cgi", http.MethodPost, diff --git a/providers/supermicro/supermicro.go b/providers/supermicro/supermicro.go index e2408a84..1ec2b367 100644 --- a/providers/supermicro/supermicro.go +++ b/providers/supermicro/supermicro.go @@ -5,7 +5,6 @@ import ( "context" "crypto/x509" "encoding/base64" - "encoding/xml" "fmt" "io" "net/http" @@ -17,6 +16,7 @@ import ( "strings" "time" + "github.com/bmc-toolbox/bmclib/v2/constants" "github.com/bmc-toolbox/bmclib/v2/internal/httpclient" "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" "github.com/bmc-toolbox/bmclib/v2/providers" @@ -94,23 +94,25 @@ func WithPort(port string) Option { // Connection details type Client struct { - client *http.Client - redfish *redfishwrapper.Client - host string - user string - pass string - port string - csrfToken string - model string - log logr.Logger + serviceClient *serviceClient + bmc bmcQueryor + log logr.Logger +} + +type bmcQueryor interface { + firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) + firmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) + firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) + firmwareTaskStatus(ctx context.Context, component, taskID string) (state, status string, err error) + // query device model from the bmc + queryDeviceModel(ctx context.Context) (model string, err error) + // returns the device model, that was queried previously with queryDeviceModel + deviceModel() (model string) + supportsInstall(component string) error } // New returns connection with a Supermicro client initialized func NewClient(host, user, pass string, log logr.Logger, opts ...Option) *Client { - if !strings.HasPrefix(host, "https://") && !strings.HasPrefix(host, "http://") { - host = "https://" + host - } - defaultConfig := &Config{ Port: "443", } @@ -119,40 +121,31 @@ func NewClient(host, user, pass string, log logr.Logger, opts ...Option) *Client opt(defaultConfig) } - return &Client{ - host: host, - user: user, - pass: pass, - port: defaultConfig.Port, - client: httpclient.Build(defaultConfig.httpClientSetupFuncs...), - log: log, - } -} - -func (c *Client) x12() *x12 { return &x12{c} } - -func (c *Client) x11() *x11 { return &x11{c} } + serviceClient := newBmcServiceClient( + host, + defaultConfig.Port, + user, + pass, + httpclient.Build(defaultConfig.httpClientSetupFuncs...), + ) -func (c *Client) redfishSession(ctx context.Context) (err error) { - c.redfish = redfishwrapper.NewClient(c.host, "", c.user, c.pass, redfishwrapper.WithHTTPClient(c.client)) - if err := c.redfish.Open(ctx); err != nil { - return err + return &Client{ + serviceClient: serviceClient, + log: log, } - - return nil } // Open a connection to a Supermicro BMC using the vendor API. func (c *Client) Open(ctx context.Context) (err error) { data := fmt.Sprintf( "name=%s&pwd=%s&check=00", - base64.StdEncoding.EncodeToString([]byte(c.user)), - base64.StdEncoding.EncodeToString([]byte(c.pass)), + base64.StdEncoding.EncodeToString([]byte(c.serviceClient.user)), + base64.StdEncoding.EncodeToString([]byte(c.serviceClient.pass)), ) headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"} - body, status, err := c.query(ctx, "cgi/login.cgi", http.MethodPost, bytes.NewBufferString(data), headers, 0) + body, status, err := c.serviceClient.query(ctx, "cgi/login.cgi", http.MethodPost, bytes.NewBufferString(data), headers, 0) if err != nil { return errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()) } @@ -166,7 +159,7 @@ func (c *Client) Open(ctx context.Context) (err error) { return errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents") } - contentsTopMenu, status, err := c.query(ctx, "cgi/url_redirect.cgi?url_name=topmenu", http.MethodGet, nil, nil, 0) + contentsTopMenu, status, err := c.serviceClient.query(ctx, "cgi/url_redirect.cgi?url_name=topmenu", http.MethodGet, nil, nil, 0) if err != nil { return errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()) } @@ -180,9 +173,9 @@ func (c *Client) Open(ctx context.Context) (err error) { return errors.Wrap(bmclibErrs.ErrLoginFailed, "could not parse CSRF-TOKEN from page") } - c.csrfToken = token + c.serviceClient.setCsrfToken(token) - c.model, err = c.deviceModel(ctx) + c.bmc, err = c.bmcQueryor(ctx) if err != nil { return errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()) } @@ -190,17 +183,51 @@ func (c *Client) Open(ctx context.Context) (err error) { return nil } +func (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) { + x11 := newX11Client(c.serviceClient, c.log) + x12 := newX12Client(c.serviceClient, c.log) + + var queryor bmcQueryor + + for _, bmc := range []bmcQueryor{x11, x12} { + var err error + + _, err = bmc.queryDeviceModel(ctx) + if err != nil { + if errors.Is(err, ErrXMLAPIUnsupported) { + continue + } + + return nil, errors.Wrap(ErrModelUnknown, err.Error()) + } + + queryor = bmc + break + } + + if queryor == nil { + return nil, errors.Wrap(ErrModelUnknown, "failed to setup query client") + } + + model := strings.ToLower(queryor.deviceModel()) + if !strings.HasPrefix(model, "x12") && !strings.HasPrefix(model, "x11") { + return nil, errors.Wrap(ErrModelUnsupported, model) + } + + return queryor, nil +} + func (c *Client) openRedfish(ctx context.Context) error { - if c.redfish != nil && c.redfish.SessionActive() == nil { + if c.serviceClient.redfish != nil && c.serviceClient.redfish.SessionActive() == nil { return nil } - rfclient := redfishwrapper.NewClient(c.host, "", c.user, c.pass) + rfclient := redfishwrapper.NewClient(c.serviceClient.host, "", c.serviceClient.user, c.serviceClient.pass) if err := rfclient.Open(ctx); err != nil { return err } - c.redfish = rfclient + c.serviceClient.redfish = rfclient return nil } @@ -234,11 +261,11 @@ func parseToken(body []byte) string { // Close a connection to a Supermicro BMC using the vendor API. func (c *Client) Close(ctx context.Context) error { - if c.client == nil { + if c.serviceClient.client == nil { return nil } - _, status, err := c.query(ctx, "cgi/logout.cgi", http.MethodGet, nil, nil, 0) + _, status, err := c.serviceClient.query(ctx, "cgi/logout.cgi", http.MethodGet, nil, nil, 0) if err != nil { return errors.Wrap(bmclibErrs.ErrLogoutFailed, err.Error()) } @@ -247,11 +274,13 @@ func (c *Client) Close(ctx context.Context) error { return errors.Wrap(bmclibErrs.ErrLogoutFailed, strconv.Itoa(status)) } - if c.redfish != nil { - err = c.redfish.Close(ctx) + if c.serviceClient.redfish != nil { + err = c.serviceClient.redfish.Close(ctx) if err != nil { return errors.Wrap(bmclibErrs.ErrLogoutFailed, err.Error()) } + + c.serviceClient.redfish = nil } return nil @@ -286,7 +315,7 @@ func (c *Client) fetchScreenPreview(ctx context.Context) ([]byte, error) { headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"} endpoint := "cgi/url_redirect.cgi?url_name=Snapshot&url_type=img" - body, status, err := c.query(ctx, endpoint, http.MethodGet, nil, headers, 0) + body, status, err := c.serviceClient.query(ctx, endpoint, http.MethodGet, nil, headers, 0) if err != nil { return nil, errors.Wrap(bmclibErrs.ErrScreenshot, strconv.Itoa(status)) } @@ -303,7 +332,7 @@ func (c *Client) initScreenPreview(ctx context.Context) error { data := "op=sys_preview&_=" - body, status, err := c.query(ctx, "cgi/op.cgi", http.MethodPost, bytes.NewBufferString(data), headers, 0) + body, status, err := c.serviceClient.query(ctx, "cgi/op.cgi", http.MethodPost, bytes.NewBufferString(data), headers, 0) if err != nil { return errors.Wrap(bmclibErrs.ErrScreenshot, err.Error()) } @@ -329,32 +358,6 @@ func (c *Client) PowerSet(ctx context.Context, state string) (ok bool, err error } } -func (c *Client) fruInfo(ctx context.Context) (*FruInfo, error) { - headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"} - - payload := "op=FRU_INFO.XML&r=(0,0)&_=" - - body, status, err := c.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBufferString(payload), headers, 0) - if err != nil { - return nil, errors.Wrap(ErrQueryFRUInfo, err.Error()) - } - - if status != 200 { - return nil, unexpectedResponseErr([]byte(payload), body, status) - } - - if !bytes.Contains(body, []byte(``)) { - return nil, unexpectedResponseErr([]byte(payload), body, status) - } - - data := &IPMI{} - if err := xml.Unmarshal(body, data); err != nil { - return nil, errors.Wrap(ErrQueryFRUInfo, err.Error()) - } - - return data.FruInfo, nil -} - // powerCycle using SMC XML API // // This method is only here for the case when firmware updates are being applied using this provider. @@ -365,7 +368,7 @@ func (c *Client) powerCycle(ctx context.Context) (bool, error) { "Content-type": "application/x-www-form-urlencoded; charset=UTF-8", } - body, status, err := c.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBuffer(payload), headers, 0) + body, status, err := c.serviceClient.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBuffer(payload), headers, 0) if err != nil { return false, err } @@ -377,25 +380,52 @@ func (c *Client) powerCycle(ctx context.Context) (bool, error) { return true, nil } -func (c *Client) deviceModel(ctx context.Context) (string, error) { - deviceModel, err := c.x11().deviceModel(ctx) - if err != nil { - if errors.Is(err, ErrXMLAPIUnsupported) { - var errRF error +type serviceClient struct { + host string + port string + user string + pass string + csrfToken string + client *http.Client + redfish *redfishwrapper.Client +} - deviceModel, errRF = c.x12().deviceModel(ctx) - if errRF != nil { - return "", errors.Wrap(errRF, "XML API not supported and failed to identify model over Redfish") - } - } +func newBmcServiceClient(host, port, user, pass string, client *http.Client) *serviceClient { + if !strings.HasPrefix(host, "https://") && !strings.HasPrefix(host, "http://") { + host = "https://" + host } - c.model = deviceModel + return &serviceClient{host: host, port: port, user: user, pass: pass, client: client} +} + +func (c *serviceClient) setCsrfToken(t string) { + c.csrfToken = t +} + +func (c *serviceClient) redfishSession(ctx context.Context) (err error) { + c.redfish = redfishwrapper.NewClient(c.host, "", c.user, c.pass, redfishwrapper.WithHTTPClient(c.client)) + if err := c.redfish.Open(ctx); err != nil { + return err + } + + return nil +} + +func (c *serviceClient) supportsFirmwareInstall(ctx context.Context, model string) error { + if model == "" { + return errors.Wrap(ErrModelUnknown, "unable to determine firmware install compatibility") + } + + for _, s := range supportedModels { + if strings.EqualFold(s, model) { + return nil + } + } - return deviceModel, nil + return errors.Wrap(ErrModelUnsupported, "firmware install not supported for: "+model) } -func (c *Client) query(ctx context.Context, endpoint, method string, payload io.Reader, headers map[string]string, contentLength int64) ([]byte, int, error) { +func (c *serviceClient) query(ctx context.Context, endpoint, method string, payload io.Reader, headers map[string]string, contentLength int64) ([]byte, int, error) { var body []byte var err error var req *http.Request diff --git a/providers/supermicro/supermicro_test.go b/providers/supermicro/supermicro_test.go index 49f98548..2317a88a 100644 --- a/providers/supermicro/supermicro_test.go +++ b/providers/supermicro/supermicro_test.go @@ -82,7 +82,7 @@ func TestOpen(t *testing.T) { // first request to login "/cgi/login.cgi": func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, r.Method, http.MethodPost) - assert.Equal(t, r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") + assert.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) b, err := io.ReadAll(r.Body) if err != nil { @@ -122,6 +122,24 @@ func TestOpen(t *testing.T) { response := []byte(``) _, _ = w.Write(response) }, + // request for model + "/cgi/ipmi.cgi": func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Method, http.MethodPost) + assert.Equal(t, "application/x-www-form-urlencoded; charset=UTF-8", r.Header.Get("Content-Type")) + + b, err := io.ReadAll(r.Body) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, `op=FRU_INFO.XML&r=(0,0)&_=`, string(b)) + + _, _ = w.Write([]byte(` + + + + `)) + }, }, }, { @@ -166,21 +184,20 @@ func TestOpen(t *testing.T) { } client := NewClient(parsedURL.Hostname(), tc.user, tc.pass, logr.Discard(), WithPort(parsedURL.Port())) - if err := client.Open(context.Background()); err != nil { - if tc.errorContains != "" { - assert.ErrorContains(t, err, tc.errorContains) - - return - } + err = client.Open(context.Background()) + if tc.errorContains != "" { + assert.ErrorContains(t, err, tc.errorContains) - assert.Nil(t, err) + return } + + assert.Nil(t, err) }) } } -func Test_Close(t *testing.T) { +func TestClose(t *testing.T) { testcases := []struct { name string errorContains string @@ -222,21 +239,21 @@ func Test_Close(t *testing.T) { } client := NewClient(parsedURL.Hostname(), tc.user, tc.pass, logr.Discard(), WithPort(parsedURL.Port())) - if err := client.Close(context.Background()); err != nil { - if tc.errorContains != "" { - assert.ErrorContains(t, err, tc.errorContains) - - return - } + err = client.Close(context.Background()) + if tc.errorContains != "" { + assert.ErrorContains(t, err, tc.errorContains) - assert.Nil(t, err) + return } + + assert.Nil(t, err) + assert.Nil(t, client.serviceClient.redfish) }) } } -func Test_initScreenPreview(t *testing.T) { +func TestInitScreenPreview(t *testing.T) { testcases := []struct { name string errorContains string @@ -287,20 +304,19 @@ func Test_initScreenPreview(t *testing.T) { } client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - if err := client.initScreenPreview(context.Background()); err != nil { - if tc.errorContains != "" { - assert.ErrorContains(t, err, tc.errorContains) + err = client.initScreenPreview(context.Background()) + if tc.errorContains != "" { + assert.ErrorContains(t, err, tc.errorContains) + return + } - return - } + assert.Nil(t, err) - assert.Nil(t, err) - } }) } } -func Test_fetchScreenPreview(t *testing.T) { +func TestFetchScreenPreview(t *testing.T) { testcases := []struct { name string expectImage []byte @@ -348,11 +364,9 @@ func Test_fetchScreenPreview(t *testing.T) { client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) image, err := client.fetchScreenPreview(context.Background()) - if err != nil { - if tc.errorContains != "" { - assert.ErrorContains(t, err, tc.errorContains) - return - } + if tc.errorContains != "" { + assert.ErrorContains(t, err, tc.errorContains) + return } assert.Nil(t, err) diff --git a/providers/supermicro/x11.go b/providers/supermicro/x11.go index b097c644..705c29f2 100644 --- a/providers/supermicro/x11.go +++ b/providers/supermicro/x11.go @@ -1,19 +1,40 @@ package supermicro import ( + "bytes" "context" + "encoding/xml" "fmt" + "net/http" + "os" "strings" "github.com/bmc-toolbox/bmclib/v2/constants" + bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" "github.com/bmc-toolbox/common" + "github.com/go-logr/logr" "github.com/pkg/errors" "golang.org/x/exp/slices" ) -type x11 struct{ *Client } +type x11 struct { + *serviceClient + model string + log logr.Logger +} -func (c *x11) deviceModel(ctx context.Context) (string, error) { +func newX11Client(client *serviceClient, logger logr.Logger) bmcQueryor { + return &x11{ + serviceClient: client, + log: logger, + } +} + +func (c *x11) deviceModel() string { + return c.model +} + +func (c *x11) queryDeviceModel(ctx context.Context) (string, error) { errBoardPartNumUnknown := errors.New("baseboard part number unknown") data, err := c.fruInfo(ctx) if err != nil { @@ -30,10 +51,38 @@ func (c *x11) deviceModel(ctx context.Context) (string, error) { return "", errors.Wrap(errBoardPartNumUnknown, "baseboard part number empty") } - return partNum, nil + c.model = common.FormatProductName(partNum) + + return c.model, nil } -func (c *x11) componentSupported(component string) error { +func (c *x11) fruInfo(ctx context.Context) (*FruInfo, error) { + headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"} + + payload := "op=FRU_INFO.XML&r=(0,0)&_=" + + body, status, err := c.query(ctx, "cgi/ipmi.cgi", http.MethodPost, bytes.NewBufferString(payload), headers, 0) + if err != nil { + return nil, errors.Wrap(ErrQueryFRUInfo, err.Error()) + } + + if status != 200 { + return nil, unexpectedResponseErr([]byte(payload), body, status) + } + + if !bytes.Contains(body, []byte(``)) { + return nil, unexpectedResponseErr([]byte(payload), body, status) + } + + data := &IPMI{} + if err := xml.Unmarshal(body, data); err != nil { + return nil, errors.Wrap(ErrQueryFRUInfo, err.Error()) + } + + return data.FruInfo, nil +} + +func (c *x11) supportsInstall(component string) error { errComponentNotSupported := fmt.Errorf("component %s on device %s not supported", component, c.model) supported := []string{common.SlugBIOS, common.SlugBMC} @@ -45,7 +94,7 @@ func (c *x11) componentSupported(component string) error { } func (c *x11) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) { - if err := c.componentSupported(component); err != nil { + if err := c.supportsInstall(component); err != nil { return nil, err } @@ -55,3 +104,42 @@ func (c *x11) firmwareInstallSteps(component string) ([]constants.FirmwareInstal constants.FirmwareInstallStepInstallStatus, }, nil } + +func (c *x11) firmwareUpload(ctx context.Context, component string, file *os.File) (string, error) { + component = strings.ToUpper(component) + + switch component { + case common.SlugBIOS: + return "", c.firmwareUploadBIOS(ctx, file) + case common.SlugBMC: + return "", c.firmwareUploadBMC(ctx, file) + } + + return "", errors.Wrap(bmclibErrs.ErrFirmwareInstall, "component unsupported: "+component) +} + +func (c *x11) firmwareInstallUploaded(ctx context.Context, component, _ string) (string, error) { + component = strings.ToUpper(component) + + switch component { + case common.SlugBIOS: + return "", c.firmwareInstallUploadedBIOS(ctx) + case common.SlugBMC: + return "", c.initiateBMCFirmwareInstall(ctx) + } + + return "", errors.Wrap(bmclibErrs.ErrFirmwareInstallUploaded, "component unsupported: "+component) +} + +func (c *x11) firmwareTaskStatus(ctx context.Context, component, _ string) (state, status string, err error) { + component = strings.ToUpper(component) + + switch component { + case common.SlugBIOS: + return c.statusBIOSFirmwareInstall(ctx) + case common.SlugBMC: + return c.statusBMCFirmwareInstall(ctx) + } + + return "", "", errors.Wrap(bmclibErrs.ErrFirmwareTaskStatus, "component unsupported: "+component) +} diff --git a/providers/supermicro/x11_firmware.go b/providers/supermicro/x11_firmware.go deleted file mode 100644 index 526297da..00000000 --- a/providers/supermicro/x11_firmware.go +++ /dev/null @@ -1,50 +0,0 @@ -package supermicro - -import ( - "context" - "io" - "strings" - - bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" - "github.com/bmc-toolbox/common" - "github.com/pkg/errors" -) - -func (c *x11) firmwareUpload(ctx context.Context, component string, reader io.Reader) (string, error) { - component = strings.ToUpper(component) - - switch component { - case common.SlugBIOS: - return "", c.firmwareUploadBIOS(ctx, reader) - case common.SlugBMC: - return "", c.firmwareUploadBMC(ctx, reader) - } - - return "", errors.Wrap(bmclibErrs.ErrFirmwareInstall, "component unsupported: "+component) -} - -func (c *x11) firmwareInstallUploaded(ctx context.Context, component string) error { - component = strings.ToUpper(component) - - switch component { - case common.SlugBIOS: - return c.firmwareInstallUploadedBIOS(ctx) - case common.SlugBMC: - return c.initiateBMCFirmwareInstall(ctx) - } - - return errors.Wrap(bmclibErrs.ErrFirmwareInstallUploaded, "component unsupported: "+component) -} - -func (c *x11) firmwareTaskStatus(ctx context.Context, component string) (state, status string, err error) { - component = strings.ToUpper(component) - - switch component { - case common.SlugBIOS: - return c.statusBIOSFirmwareInstall(ctx) - case common.SlugBMC: - return c.statusBMCFirmwareInstall(ctx) - } - - return "", "", errors.Wrap(bmclibErrs.ErrFirmwareTaskStatus, "component unsupported: "+component) -} diff --git a/providers/supermicro/x11_firmware_bmc_test.go b/providers/supermicro/x11_firmware_bmc_test.go index ac87cb1a..b6b0ad13 100644 --- a/providers/supermicro/x11_firmware_bmc_test.go +++ b/providers/supermicro/x11_firmware_bmc_test.go @@ -14,6 +14,7 @@ import ( "testing" "github.com/bmc-toolbox/bmclib/v2/constants" + "github.com/bmc-toolbox/bmclib/v2/internal/httpclient" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" ) @@ -90,8 +91,10 @@ func TestX11SetBMCFirmwareInstallMode(t *testing.T) { t.Fatal(err) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - if err := client.x11().setBMCFirmwareInstallMode(context.Background()); err != nil { + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + if err := client.setBMCFirmwareInstallMode(context.Background()); err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) @@ -181,9 +184,11 @@ func TestX11UploadBMCFirmware(t *testing.T) { defer os.Remove(binPath) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - client.csrfToken = "foobar" - if err := client.x11().uploadBMCFirmware(context.Background(), fwReader); err != nil { + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + serviceClient.csrfToken = "foobar" + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + if err := client.uploadBMCFirmware(context.Background(), fwReader); err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) @@ -260,9 +265,11 @@ func TestX11VerifyBMCFirmwareVersion(t *testing.T) { t.Fatal(err) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - client.csrfToken = "foobar" - if err := client.x11().verifyBMCFirmwareVersion(context.Background()); err != nil { + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + serviceClient.csrfToken = "foobar" + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + if err := client.verifyBMCFirmwareVersion(context.Background()); err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) @@ -339,9 +346,11 @@ func TestX11InitiateBMCFirmwareInstall(t *testing.T) { t.Fatal(err) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - client.csrfToken = "foobar" - if err := client.x11().initiateBMCFirmwareInstall(context.Background()); err != nil { + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + serviceClient.csrfToken = "foobar" + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + if err := client.initiateBMCFirmwareInstall(context.Background()); err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) @@ -500,9 +509,11 @@ func TestX11StatusBMCFirmwareInstall(t *testing.T) { t.Fatal(err) } - client := NewClient(parsedURL.Hostname(), "foo", "bar", logr.Discard(), WithPort(parsedURL.Port())) - client.csrfToken = "foobar" - gotState, gotStatus, err := client.x11().statusBMCFirmwareInstall(context.Background()) + serviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), "foo", "bar", httpclient.Build()) + serviceClient.csrfToken = "foobar" + client := &x11{serviceClient: serviceClient, log: logr.Discard()} + + gotState, gotStatus, err := client.statusBMCFirmwareInstall(context.Background()) if err != nil { if tc.errorContains != "" { assert.ErrorContains(t, err, tc.errorContains) diff --git a/providers/supermicro/x12.go b/providers/supermicro/x12.go index c997f61b..e027d858 100644 --- a/providers/supermicro/x12.go +++ b/providers/supermicro/x12.go @@ -2,14 +2,39 @@ package supermicro import ( "context" + "encoding/json" + "fmt" + "os" + "strings" + "github.com/bmc-toolbox/bmclib/v2/constants" + brrs "github.com/bmc-toolbox/bmclib/v2/errors" + rfw "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" "github.com/bmc-toolbox/common" + "github.com/go-logr/logr" "github.com/pkg/errors" + "github.com/stmcginnis/gofish/redfish" + "golang.org/x/exp/slices" ) -type x12 struct{ *Client } +type x12 struct { + *serviceClient + model string + log logr.Logger +} + +func newX12Client(client *serviceClient, logger logr.Logger) bmcQueryor { + return &x12{ + serviceClient: client, + log: logger, + } +} -func (c *x12) deviceModel(ctx context.Context) (string, error) { +func (c *x12) deviceModel() string { + return c.model +} + +func (c *x12) queryDeviceModel(ctx context.Context) (string, error) { if err := c.redfishSession(ctx); err != nil { return "", err } @@ -23,5 +48,256 @@ func (c *x12) deviceModel(ctx context.Context) (string, error) { return "", errors.Wrap(ErrModelUnknown, "empty value") } - return common.FormatProductName(model), nil + c.model = common.FormatProductName(model) + + return c.model, nil +} + +var ( + errUploadTaskIDEmpty = errors.New("firmware upload request returned empty firmware upload verify TaskID") +) + +func (c *x12) supportsInstall(component string) error { + errComponentNotSupported := fmt.Errorf("component %s on device %s not supported", component, c.model) + + supported := []string{common.SlugBIOS, common.SlugBMC} + if !slices.Contains(supported, strings.ToUpper(component)) { + return errComponentNotSupported + } + + return nil +} + +func (c *x12) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) { + if err := c.supportsInstall(component); err != nil { + return nil, err + } + + return []constants.FirmwareInstallStep{ + constants.FirmwareInstallStepUpload, + constants.FirmwareInstallStepUploadStatus, + constants.FirmwareInstallStepInstallUploaded, + constants.FirmwareInstallStepInstallStatus, + }, nil +} + +// upload firmware +func (c *x12) firmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) { + if err = c.supportsInstall(component); err != nil { + return "", err + } + + err = c.firmwareTaskActive(ctx, component) + if err != nil { + return "", err + } + + targetID, err := c.redfishOdataID(ctx, component) + if err != nil { + return "", err + } + + params, err := c.redfishParameters(component, targetID) + if err != nil { + return "", err + } + + taskID, err = c.redfish.FirmwareUpload(ctx, file, params) + if err != nil { + if strings.Contains(err.Error(), "OemFirmwareAlreadyInUpdateMode") { + return "", errors.Wrap(brrs.ErrBMCColdResetRequired, "BMC currently in update mode, either continue the update OR if no update is currently running - reset the BMC") + } + + return "", errors.Wrap(err, "error in firmware upload") + } + + if taskID == "" { + return "", errUploadTaskIDEmpty + } + + return taskID, nil +} + +// returns an error when a bmc firmware install is active +func (c *x12) firmwareTaskActive(ctx context.Context, component string) error { + tasks, err := c.redfish.Tasks(ctx) + if err != nil { + return errors.Wrap(err, "error querying redfish tasks") + } + + for _, t := range tasks { + t := t + + if stateFinalized(t.TaskState) { + continue + } + + if err := noTasksRunning(component, t); err != nil { + return err + } + } + + return nil +} + +// noTasksRunning returns an error if a firmware related task was found active +func noTasksRunning(component string, t *redfish.Task) error { + errTaskActive := errors.New("A firmware task was found active for component: " + component) + + const ( + // The redfish task name when the BMC is verifies the uploaded BMC firmware. + verifyBMCFirmware = "BMC Verify" + // The redfish task name when the BMC is installing the uploaded BMC firmware. + updateBMCFirmware = "BMC Update" + // The redfish task name when the BMC is verifies the uploaded BIOS firmware. + verifyBIOSFirmware = "BIOS Verify" + // The redfish task name when the BMC is installing the uploaded BIOS firmware. + updateBIOSFirmware = "BIOS Update" + ) + + var verifyTaskName, updateTaskName string + + switch strings.ToUpper(component) { + case common.SlugBMC: + verifyTaskName = verifyBMCFirmware + updateTaskName = updateBMCFirmware + case common.SlugBIOS: + verifyTaskName = verifyBIOSFirmware + updateTaskName = updateBIOSFirmware + } + + taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", t.ID, t.TaskState, t.TaskStatus) + + switch t.Name { + case verifyTaskName: + return errors.Wrap(errTaskActive, taskInfo) + case updateTaskName: + return errors.Wrap(errTaskActive, taskInfo) + default: + return nil + } +} + +func stateFinalized(s redfish.TaskState) bool { + finalized := []redfish.TaskState{ + redfish.CompletedTaskState, + redfish.CancelledTaskState, + redfish.InterruptedTaskState, + redfish.ExceptionTaskState, + } + + return slices.Contains(finalized, s) +} + +// redfish OEM parameter structs +type BIOS struct { + PreserveME bool `json:"PreserveME"` + PreserveNVRAM bool `json:"PreserveNVRAM"` + PreserveSMBIOS bool `json:"PreserveSMBIOS"` + PreserveOA bool `json:"PreserveOA"` + PreserveSETUPCONF bool `json:"PreserveSETUPCONF"` + PreserveSETUPPWD bool `json:"PreserveSETUPPWD"` + PreserveSECBOOTKEY bool `json:"PreserveSECBOOTKEY"` + PreserveBOOTCONF bool `json:"PreserveBOOTCONF"` +} + +type BMC struct { + PreserveCfg bool `json:"PreserveCfg"` + PreserveSdr bool `json:"PreserveSdr"` + PreserveSsl bool `json:"PreserveSsl"` +} + +type Supermicro struct { + *BIOS `json:"BIOS,omitempty"` + *BMC `json:"BMC,omitempty"` +} + +type OEM struct { + Supermicro `json:"Supermicro"` +} + +func (c *x12) redfishParameters(component, targetODataID string) (*rfw.RedfishUpdateServiceParameters, error) { + errUnsupported := errors.New("redfish parameters for x12 hardware component not supported: " + component) + + oem := OEM{} + + switch strings.ToUpper(component) { + case common.SlugBIOS: + oem.Supermicro.BIOS = &BIOS{ + PreserveME: false, + PreserveNVRAM: false, + PreserveSMBIOS: true, + PreserveOA: true, + PreserveSETUPCONF: true, + PreserveSETUPPWD: true, + PreserveSECBOOTKEY: true, + PreserveBOOTCONF: true, + } + case common.SlugBMC: + oem.Supermicro.BMC = &BMC{ + PreserveCfg: true, + PreserveSdr: true, + PreserveSsl: true, + } + default: + return nil, errUnsupported + } + + b, err := json.Marshal(oem) + if err != nil { + return nil, errors.Wrap(err, "error preparing redfish parameters") + } + + return &rfw.RedfishUpdateServiceParameters{ + OperationApplyTime: constants.OnStartUpdateRequest, + Targets: []string{targetODataID}, + Oem: b, + }, nil +} + +func (c *x12) redfishOdataID(ctx context.Context, component string) (string, error) { + errUnsupported := errors.New("unable to return redfish OData ID for unsupported component: " + component) + + switch strings.ToUpper(component) { + case common.SlugBMC: + return c.redfish.ManagerOdataID(ctx) + case common.SlugBIOS: + // hardcoded since SMCs without the DCMS license will throw license errors + return "/redfish/v1/Systems/1/Bios", nil + //return c.redfish.SystemsBIOSOdataID(ctx) + } + + return "", errUnsupported +} + +func (c *x12) firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) { + if err = c.supportsInstall(component); err != nil { + return "", err + } + + task, err := c.redfish.Task(ctx, uploadTaskID) + if err != nil { + e := fmt.Sprintf("error querying redfish tasks for firmware upload taskID: %s, err: %s", uploadTaskID, err.Error()) + return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, e) + } + + taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", task.ID, task.TaskState, task.TaskStatus) + + if task.TaskState != redfish.CompletedTaskState { + return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo) + } + + if task.TaskStatus != "OK" { + return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo) + } + + return c.redfish.StartUpdateForUploadedFirmware(ctx) +} + +func (c *x12) firmwareTaskStatus(ctx context.Context, component, taskID string) (state, status string, err error) { + if err = c.supportsInstall(component); err != nil { + return "", "", errors.Wrap(brrs.ErrFirmwareTaskStatus, err.Error()) + } + + return c.redfish.TaskStatus(ctx, taskID) } diff --git a/providers/supermicro/x12_firmware.go b/providers/supermicro/x12_firmware.go deleted file mode 100644 index 09904e89..00000000 --- a/providers/supermicro/x12_firmware.go +++ /dev/null @@ -1,266 +0,0 @@ -package supermicro - -import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - - "github.com/bmc-toolbox/bmclib/v2/constants" - brrs "github.com/bmc-toolbox/bmclib/v2/errors" - rfw "github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper" - "github.com/bmc-toolbox/common" - "github.com/pkg/errors" - "github.com/stmcginnis/gofish/redfish" - "golang.org/x/exp/slices" -) - -var ( - errUploadTaskIDEmpty = errors.New("firmware upload request returned empty firmware upload verify TaskID") -) - -func (c *x12) componentSupported(component string) error { - errComponentNotSupported := fmt.Errorf("component %s on device %s not supported", component, c.model) - - supported := []string{common.SlugBIOS, common.SlugBMC} - if !slices.Contains(supported, strings.ToUpper(component)) { - return errComponentNotSupported - } - - return nil -} - -func (c *x12) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) { - if err := c.componentSupported(component); err != nil { - return nil, err - } - - return []constants.FirmwareInstallStep{ - constants.FirmwareInstallStepUpload, - constants.FirmwareInstallStepUploadStatus, - constants.FirmwareInstallStepInstallUploaded, - constants.FirmwareInstallStepInstallStatus, - }, nil -} - -// upload firmware -func (c *x12) firmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) { - if err = c.componentSupported(component); err != nil { - return "", err - } - - err = c.firmwareTaskActive(ctx, component) - if err != nil { - return "", err - } - - targetID, err := c.redfishOdataID(ctx, component) - if err != nil { - return "", err - } - - params, err := c.redfishParameters(component, targetID) - if err != nil { - return "", err - } - - taskID, err = c.redfish.FirmwareUpload(ctx, file, params) - if err != nil { - if strings.Contains(err.Error(), "OemFirmwareAlreadyInUpdateMode") { - return "", errors.Wrap(brrs.ErrBMCColdResetRequired, "BMC currently in update mode, either continue the update OR if no update is currently running - reset the BMC") - } - - return "", errors.Wrap(err, "error in firmware upload") - } - - if taskID == "" { - return "", errUploadTaskIDEmpty - } - - return taskID, nil -} - -// returns an error when a bmc firmware install is active -func (c *x12) firmwareTaskActive(ctx context.Context, component string) error { - tasks, err := c.redfish.Tasks(ctx) - if err != nil { - return errors.Wrap(err, "error querying redfish tasks") - } - - for _, t := range tasks { - t := t - - if stateFinalized(t.TaskState) { - continue - } - - if err := noTasksRunning(component, t); err != nil { - return err - } - } - - return nil -} - -// noTasksRunning returns an error if a firmware related task was found active -func noTasksRunning(component string, t *redfish.Task) error { - errTaskActive := errors.New("A firmware task was found active for component: " + component) - - const ( - // The redfish task name when the BMC is verifies the uploaded BMC firmware. - verifyBMCFirmware = "BMC Verify" - // The redfish task name when the BMC is installing the uploaded BMC firmware. - updateBMCFirmware = "BMC Update" - // The redfish task name when the BMC is verifies the uploaded BIOS firmware. - verifyBIOSFirmware = "BIOS Verify" - // The redfish task name when the BMC is installing the uploaded BIOS firmware. - updateBIOSFirmware = "BIOS Update" - ) - - var verifyTaskName, updateTaskName string - - switch strings.ToUpper(component) { - case common.SlugBMC: - verifyTaskName = verifyBMCFirmware - updateTaskName = updateBMCFirmware - case common.SlugBIOS: - verifyTaskName = verifyBIOSFirmware - updateTaskName = updateBIOSFirmware - } - - taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", t.ID, t.TaskState, t.TaskStatus) - - switch t.Name { - case verifyTaskName: - return errors.Wrap(errTaskActive, taskInfo) - case updateTaskName: - return errors.Wrap(errTaskActive, taskInfo) - default: - return nil - } -} - -func stateFinalized(s redfish.TaskState) bool { - finalized := []redfish.TaskState{ - redfish.CompletedTaskState, - redfish.CancelledTaskState, - redfish.InterruptedTaskState, - redfish.ExceptionTaskState, - } - - return slices.Contains(finalized, s) -} - -// redfish OEM parameter structs -type BIOS struct { - PreserveME bool `json:"PreserveME"` - PreserveNVRAM bool `json:"PreserveNVRAM"` - PreserveSMBIOS bool `json:"PreserveSMBIOS"` - PreserveOA bool `json:"PreserveOA"` - PreserveSETUPCONF bool `json:"PreserveSETUPCONF"` - PreserveSETUPPWD bool `json:"PreserveSETUPPWD"` - PreserveSECBOOTKEY bool `json:"PreserveSECBOOTKEY"` - PreserveBOOTCONF bool `json:"PreserveBOOTCONF"` -} - -type BMC struct { - PreserveCfg bool `json:"PreserveCfg"` - PreserveSdr bool `json:"PreserveSdr"` - PreserveSsl bool `json:"PreserveSsl"` -} - -type Supermicro struct { - *BIOS `json:"BIOS,omitempty"` - *BMC `json:"BMC,omitempty"` -} - -type OEM struct { - Supermicro `json:"Supermicro"` -} - -func (c *x12) redfishParameters(component, targetODataID string) (*rfw.RedfishUpdateServiceParameters, error) { - errUnsupported := errors.New("redfish parameters for x12 hardware component not supported: " + component) - - oem := OEM{} - - switch strings.ToUpper(component) { - case common.SlugBIOS: - oem.Supermicro.BIOS = &BIOS{ - PreserveME: false, - PreserveNVRAM: false, - PreserveSMBIOS: true, - PreserveOA: true, - PreserveSETUPCONF: true, - PreserveSETUPPWD: true, - PreserveSECBOOTKEY: true, - PreserveBOOTCONF: true, - } - case common.SlugBMC: - oem.Supermicro.BMC = &BMC{ - PreserveCfg: true, - PreserveSdr: true, - PreserveSsl: true, - } - default: - return nil, errUnsupported - } - - b, err := json.Marshal(oem) - if err != nil { - return nil, errors.Wrap(err, "error preparing redfish parameters") - } - - return &rfw.RedfishUpdateServiceParameters{ - OperationApplyTime: constants.OnStartUpdateRequest, - Targets: []string{targetODataID}, - Oem: b, - }, nil -} - -func (c *x12) redfishOdataID(ctx context.Context, component string) (string, error) { - errUnsupported := errors.New("unable to return redfish OData ID for unsupported component: " + component) - - switch strings.ToUpper(component) { - case common.SlugBMC: - return c.redfish.ManagerOdataID(ctx) - case common.SlugBIOS: - // hardcoded since SMCs without the DCMS license will throw license errors - return "/redfish/v1/Systems/1/Bios", nil - //return c.redfish.SystemsBIOSOdataID(ctx) - } - - return "", errUnsupported -} - -func (c *x12) firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) { - if err = c.componentSupported(component); err != nil { - return "", err - } - - task, err := c.redfish.Task(ctx, uploadTaskID) - if err != nil { - e := fmt.Sprintf("error querying redfish tasks for firmware upload taskID: %s, err: %s", uploadTaskID, err.Error()) - return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, e) - } - - taskInfo := fmt.Sprintf("id: %s, state: %s, status: %s", task.ID, task.TaskState, task.TaskStatus) - - if task.TaskState != redfish.CompletedTaskState { - return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo) - } - - if task.TaskStatus != "OK" { - return "", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo) - } - - return c.redfish.StartUpdateForUploadedFirmware(ctx) -} - -func (c *x12) firmwareTaskStatus(ctx context.Context, component, taskID string) (state, status string, err error) { - if err = c.componentSupported(component); err != nil { - return "", "", errors.Wrap(brrs.ErrFirmwareTaskStatus, err.Error()) - } - - return c.redfish.TaskStatus(ctx, taskID) -}