Merge "Add iDRAC ephemeral boot media support"
This commit is contained in:
commit
3040cfbfef
@ -30,6 +30,7 @@ import (
|
||||
const (
|
||||
// ClientType is used by other packages as the identifier of the Redfish client.
|
||||
ClientType string = "redfish"
|
||||
mediaEjectDelay = 30 * time.Second
|
||||
systemActionRetries = 30
|
||||
systemRebootDelay = 2 * time.Second
|
||||
)
|
||||
@ -140,10 +141,27 @@ func (c *Client) SetVirtualMedia(ctx context.Context, isoPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Eject virtual media if it is already inserted
|
||||
vMediaMgr, httpResp, err := c.RedfishAPI.GetManagerVirtualMedia(ctx, managerID, vMediaID)
|
||||
if err = ScreenRedfishError(httpResp, err); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *vMediaMgr.Inserted == true {
|
||||
var emptyBody map[string]interface{}
|
||||
_, httpResp, err = c.RedfishAPI.EjectVirtualMedia(ctx, managerID, vMediaID, emptyBody)
|
||||
if err = ScreenRedfishError(httpResp, err); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(mediaEjectDelay)
|
||||
}
|
||||
|
||||
vMediaReq := redfishClient.InsertMediaRequestBody{}
|
||||
vMediaReq.Image = isoPath
|
||||
vMediaReq.Inserted = true
|
||||
_, httpResp, err := c.RedfishAPI.InsertVirtualMedia(ctx, managerID, vMediaID, vMediaReq)
|
||||
_, httpResp, err = c.RedfishAPI.InsertVirtualMedia(ctx, managerID, vMediaID, vMediaReq)
|
||||
|
||||
return ScreenRedfishError(httpResp, err)
|
||||
}
|
||||
|
||||
|
@ -267,6 +267,8 @@ func TestSetVirtualMediaInsertVirtualMediaError(t *testing.T) {
|
||||
Return(testutil.GetMediaCollection([]string{"Cd"}), httpResp, nil)
|
||||
m.On("GetManagerVirtualMedia", ctx, testutil.ManagerID, "Cd").Times(1).
|
||||
Return(testutil.GetVirtualMedia([]string{"CD"}), httpResp, nil)
|
||||
m.On("GetManagerVirtualMedia", ctx, testutil.ManagerID, "Cd").Times(1).
|
||||
Return(testutil.GetVirtualMedia([]string{"CD"}), httpResp, nil)
|
||||
m.On("InsertVirtualMedia", context.Background(), testutil.ManagerID, "Cd", mock.Anything).Return(
|
||||
redfishClient.RedfishError{}, &http.Response{StatusCode: 500}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
|
85
pkg/remote/redfish/vendors/dell/client.go
vendored
85
pkg/remote/redfish/vendors/dell/client.go
vendored
@ -15,17 +15,35 @@
|
||||
package dell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
redfishAPI "opendev.org/airship/go-redfish/api"
|
||||
redfishClient "opendev.org/airship/go-redfish/client"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/log"
|
||||
"opendev.org/airship/airshipctl/pkg/remote/redfish"
|
||||
)
|
||||
|
||||
const (
|
||||
// ClientType is used by other packages as the identifier of the Redfish client.
|
||||
ClientType string = "redfish-dell"
|
||||
ClientType = "redfish-dell"
|
||||
endpointImportSysCFG = "%s/redfish/v1/Managers/%s/Actions/Oem/EID_674_Manager.ImportSystemConfiguration"
|
||||
vCDBootRequestBody = `{
|
||||
"ShareParameters": {
|
||||
"Target": "ALL"
|
||||
},
|
||||
"ImportBuffer": "<SystemConfiguration>
|
||||
<Component FQDD=\"iDRAC.Embedded.1\">
|
||||
<Attribute Name=\"ServerBoot.1#BootOnce\">Enabled</Attribute>
|
||||
<Attribute Name=\"ServerBoot.1#FirstBootDevice\">VCD-DVD</Attribute>
|
||||
</Component>
|
||||
</SystemConfiguration>"
|
||||
}`
|
||||
)
|
||||
|
||||
// Client is a wrapper around the standard airshipctl Redfish client. This allows vendor specific Redfish clients to
|
||||
@ -36,6 +54,71 @@ type Client struct {
|
||||
RedfishCFG *redfishClient.Configuration
|
||||
}
|
||||
|
||||
type iDRACAPIRespErr struct {
|
||||
Err iDRACAPIErr `json:"error"`
|
||||
}
|
||||
|
||||
type iDRACAPIErr struct {
|
||||
ExtendedInfo []iDRACAPIExtendedInfo `json:"@Message.ExtendedInfo"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type iDRACAPIExtendedInfo struct {
|
||||
Message string `json:"Message"`
|
||||
Resolution string `json:"Resolution,omitempty"`
|
||||
}
|
||||
|
||||
// SetEphemeralBootSourceByType sets the boot source of the ephemeral node to a virtual CD, "VCD-DVD".
|
||||
func (c *Client) SetEphemeralBootSourceByType(ctx context.Context) error {
|
||||
managerID, err := redfish.GetManagerID(ctx, c.RedfishAPI, c.EphemeralNodeID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NOTE(drewwalters96): Setting the boot device to a virtual media type requires an API request to the iDRAC
|
||||
// actions API. The request is made below using the same HTTP client used by the Redfish API and exposed by the
|
||||
// standard airshipctl Redfish client. Only iDRAC 9 >= 3.3 is supports this endpoint.
|
||||
url := fmt.Sprintf(endpointImportSysCFG, c.RedfishCFG.BasePath, managerID)
|
||||
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(vCDBootRequestBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Accept", "application/json")
|
||||
|
||||
if auth, ok := ctx.Value(redfishClient.ContextBasicAuth).(redfishClient.BasicAuth); ok {
|
||||
req.SetBasicAuth(auth.UserName, auth.Password)
|
||||
}
|
||||
|
||||
httpResp, err := c.RedfishCFG.HTTPClient.Do(req)
|
||||
if httpResp.StatusCode != http.StatusAccepted {
|
||||
body, ok := ioutil.ReadAll(httpResp.Body)
|
||||
if ok != nil {
|
||||
log.Debugf("Malformed iDRAC response: %s", body)
|
||||
return redfish.ErrRedfishClient{Message: "Unable to set boot device. Malformed iDRAC response."}
|
||||
}
|
||||
|
||||
var iDRACResp iDRACAPIRespErr
|
||||
ok = json.Unmarshal(body, &iDRACResp)
|
||||
if ok != nil {
|
||||
log.Debugf("Malformed iDRAC response: %s", body)
|
||||
return redfish.ErrRedfishClient{Message: "Unable to set boot device. Malformed iDrac response."}
|
||||
}
|
||||
|
||||
return redfish.ErrRedfishClient{
|
||||
Message: fmt.Sprintf("Unable to set boot device. %s", iDRACResp.Err.ExtendedInfo[0]),
|
||||
}
|
||||
} else if err != nil {
|
||||
return redfish.ErrRedfishClient{Message: fmt.Sprintf("Unable to set boot device. %v", err)}
|
||||
}
|
||||
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClient returns a client with the capability to make Redfish requests.
|
||||
func NewClient(ephemeralNodeID string,
|
||||
isoPath string,
|
||||
|
26
pkg/remote/redfish/vendors/dell/client_test.go
vendored
26
pkg/remote/redfish/vendors/dell/client_test.go
vendored
@ -13,9 +13,13 @@
|
||||
package dell
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
redfishMocks "opendev.org/airship/go-redfish/api/mocks"
|
||||
redfishClient "opendev.org/airship/go-redfish/client"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,10 +29,24 @@ const (
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
// NOTE(drewwalters96): The Dell client implementation of this method simply creates the standard Redfish
|
||||
// client. This test verifies that the Dell client creates and stores an instance of the standard client.
|
||||
|
||||
// Create the Dell client
|
||||
_, _, err := NewClient(ephemeralNodeID, isoPath, redfishURL, false, false, "username", "password")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSetEphemeralBootSourceByTypeGetSystemError(t *testing.T) {
|
||||
m := &redfishMocks.RedfishAPI{}
|
||||
defer m.AssertExpectations(t)
|
||||
|
||||
ctx, client, err := NewClient("invalid-server", isoPath, redfishURL, false, false, "", "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Mock redfish get system request
|
||||
m.On("GetSystem", ctx, client.EphemeralNodeID()).Times(1).Return(redfishClient.ComputerSystem{},
|
||||
&http.Response{StatusCode: 500}, redfishClient.GenericOpenAPIError{})
|
||||
|
||||
// Replace normal API client with mocked API client
|
||||
client.RedfishAPI = m
|
||||
|
||||
err = client.SetEphemeralBootSourceByType(ctx)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
@ -48,7 +48,10 @@ func GetVirtualMedia(types []string) redfishClient.VirtualMedia {
|
||||
mediaTypes = append(mediaTypes, t)
|
||||
}
|
||||
|
||||
inserted := false
|
||||
|
||||
vMedia.MediaTypes = mediaTypes
|
||||
vMedia.Inserted = &inserted
|
||||
|
||||
return vMedia
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user