Skip to content

Commit

Permalink
Current state of work
Browse files Browse the repository at this point in the history
  • Loading branch information
ofaurax committed Aug 10, 2023
1 parent 7448c71 commit c57b61d
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 15 deletions.
94 changes: 80 additions & 14 deletions providers/redfish/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,23 @@ func (c *Conn) FirmwareInstall(ctx context.Context, component, applyAt string, f
"UpdateFile": reader,
}

resp, err := c.runRequestWithMultipartPayload(http.MethodPost, "/redfish/v1/UpdateService/MultipartUpload", payload)
updateService, err := c.redfishwrapper.UpdateService()

if err != nil {
return "", errors.Wrap(bmclibErrs.ErrFirmwareUpload, err.Error())

Check warning on line 112 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L112

Added line #L112 was not covered by tests
}

var resp *http.Response
if updateService.MultipartHTTPPushURI != "" {
// TODO: should use updateService.MultipartHTTPPushURI rather than hardcoded path
// but should be tested when modified
resp, err = c.runRequestWithMultipartPayload(http.MethodPost, "/redfish/v1/UpdateService/MultipartUpload", payload)
} else if updateService.HTTPPushURI != "" {
resp, err = c.runRequestWithPayload(http.MethodPost, updateService.HTTPPushURI, payload["UpdateFile"])
} else {
return "", errors.Wrap(bmclibErrs.ErrFirmwareUpload, "No URI available for push updates")

Check warning on line 123 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L121-L123

Added lines #L121 - L123 were not covered by tests
}

if err != nil {
return "", errors.Wrap(bmclibErrs.ErrFirmwareUpload, err.Error())
}
Expand All @@ -118,10 +134,21 @@ func (c *Conn) FirmwareInstall(ctx context.Context, component, applyAt string, f
)
}

// The response contains a location header pointing to the task URI
// Location: /redfish/v1/TaskService/Tasks/JID_467696020275
if strings.Contains(resp.Header.Get("Location"), "JID_") {
taskID = strings.Split(resp.Header.Get("Location"), "JID_")[1]
return location2TaskID(resp.Header.Get("Location"))
}

func location2TaskID(location string) (taskID string, err error) {
if strings.Contains(location, "JID_") {
// The response contains a location header pointing to the task URI
// Location: /redfish/v1/TaskService/Tasks/JID_467696020275
taskID = strings.Split(location, "JID_")[1]
} else if strings.Contains(location, "/Monitor") {
// OpenBMC returns a monitor URL in Location
// Location: /redfish/v1/TaskService/Tasks/12/Monitor
splits := strings.Split(location, "/")
taskID = splits[5]
} else {
return "", bmclibErrs.ErrTaskNotFound

Check warning on line 151 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L148-L151

Added lines #L148 - L151 were not covered by tests
}

return taskID, nil
Expand All @@ -134,10 +161,41 @@ func (c *Conn) FirmwareInstallStatus(ctx context.Context, installVersion, compon
return state, errors.Wrap(err, "unable to determine device vendor, model attributes")
}

// component is not used, we hack it for tests
if component == "testOpenbmc" {
vendor = constants.Packet
}

var task *gofishrf.Task
switch {
case strings.Contains(vendor, constants.Dell):
task, err = c.dellJobAsRedfishTask(taskID)
if task == nil {
return state, errors.New("failed to lookup task status for task ID: " + taskID)

Check warning on line 174 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L173-L174

Added lines #L173 - L174 were not covered by tests
}

state = strings.ToLower(string(task.TaskState))

Check warning on line 177 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L177

Added line #L177 was not covered by tests

case strings.Contains(vendor, constants.Packet):
resp, _ := c.redfishwrapper.Get("/redfish/v1/TaskService/Tasks/" + taskID)
if resp.StatusCode != 200 {
err = errors.Wrap(
bmclibErrs.ErrFirmwareInstall,
"HTTP Error: "+fmt.Sprint(resp.StatusCode),
)

Check warning on line 185 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L182-L185

Added lines #L182 - L185 were not covered by tests

state = "failed"
break

Check warning on line 188 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L187-L188

Added lines #L187 - L188 were not covered by tests
}

//task, err := gofishrf.GetTask(c.redfishwrapper, "/redfish/v1/TaskService/Tasks/" + taskID)
//fmt.Printf("task:", task);

data, _ := io.ReadAll(resp.Body)
resp.Body.Close()

state, err = c.openbmcGetStatus(data)

default:
err = errors.Wrap(
bmclibErrs.ErrNotImplemented,
Expand All @@ -149,12 +207,6 @@ func (c *Conn) FirmwareInstallStatus(ctx context.Context, installVersion, compon
return state, err
}

if task == nil {
return state, errors.New("failed to lookup task status for task ID: " + taskID)
}

state = strings.ToLower(string(task.TaskState))

// so much for standards...
switch state {
case "starting", "downloading", "downloaded":
Expand Down Expand Up @@ -189,9 +241,10 @@ func (c *Conn) firmwareUpdateCompatible(ctx context.Context) (err error) {
return errors.Wrap(bmclibErrs.ErrRedfishUpdateService, "service disabled")
}

// for now we expect multipart HTTP push update support
if updateService.MultipartHTTPPushURI == "" {
return errors.Wrap(bmclibErrs.ErrRedfishUpdateService, "Multipart HTTP push updates not supported")
// for now we expect multipart HTTP push update support,
// or at least the unstructured HTTP push update support
if updateService.MultipartHTTPPushURI == "" && updateService.HTTPPushURI == "" {
return errors.Wrap(bmclibErrs.ErrRedfishUpdateService, "No HTTP push updates supported (multipart or unstructured)")

Check warning on line 247 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L247

Added line #L247 was not covered by tests
}

return nil
Expand Down Expand Up @@ -248,6 +301,17 @@ func (c *Conn) runRequestWithMultipartPayload(method, url string, payload map[st
return c.redfishwrapper.RunRawRequestWithHeaders(method, url, bytes.NewReader(payloadBuffer.Bytes()), payloadWriter.FormDataContentType(), nil)
}

// Updates using an unstrctured HTTP updates
func (c *Conn) runRequestWithPayload(method, url string, payload io.Reader) (*http.Response, error) {
if url == "" {
return nil, fmt.Errorf("unable to execute request, no target provided")
}

b, _ := io.ReadAll(payload)
payloadReadSeeker := bytes.NewReader(b)
return c.redfishwrapper.RunRawRequestWithHeaders(method, url, payloadReadSeeker, "application/octet-stream", nil)

Check warning on line 312 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L310-L312

Added lines #L310 - L312 were not covered by tests
}

// sets up the UpdateParameters MIMEHeader for the multipart form
// the Go multipart writer CreateFormField does not currently let us set Content-Type on a MIME Header
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.8:src/mime/multipart/writer.go;l=151
Expand Down Expand Up @@ -276,6 +340,8 @@ func (c *Conn) GetFirmwareInstallTaskQueued(ctx context.Context, component strin
switch {
case strings.Contains(vendor, constants.Dell):
task, err = c.getDellFirmwareInstallTaskScheduled(component)
case strings.Contains(vendor, constants.Packet):

Check warning on line 343 in providers/redfish/firmware.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/firmware.go#L343

Added line #L343 was not covered by tests
//task, err = c.getDellFirmwareInstallTaskScheduled(component)
default:
err = errors.Wrap(
bmclibErrs.ErrNotImplemented,
Expand Down
46 changes: 45 additions & 1 deletion providers/redfish/firmware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/bmc-toolbox/common"
)

// handler registered in mock_test.go
// handler registered in main_test.go
func multipartUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusNotFound)
Expand Down Expand Up @@ -157,3 +157,47 @@ func Test_firmwareUpdateCompatible(t *testing.T) {
t.Fatal(err)
}
}

func Test_runRequestWithPayload(t *testing.T) {
var reader io.Reader
resp, err := mockClient.runRequestWithPayload(http.MethodPost, "", reader)
if resp != nil {
t.Fatal(err)
}
}

// referenced in main_test.go
func openbmcStatus(w http.ResponseWriter, r *http.Request) {
mytask := `{
"@odata.id": "/redfish/v1/TaskService/Tasks/15",
"@odata.type": "#Task.v1_4_3.Task",
"Id": "15",
"Messages": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The task with Id '15' has started.",
"MessageArgs": [
"15"
],
"MessageId": "TaskEvent.1.0.3.TaskStarted",
"MessageSeverity": "OK",
"Resolution": "None."
}
],
"Name": "Task 15",
"TaskState": "TestState",
"TaskStatus": "TestStatus"
}
`
_, _ = w.Write([]byte(mytask))
}

func Test_FirmwareInstall2(t *testing.T) {
state, err := mockClient.FirmwareInstallStatus(context.TODO(), "", "testOpenbmc", "15")
if err != nil {
t.Fatal(err)
}
if state != "unknown: teststate" {
t.Fatal("Wrong test state:", state)
}
}
1 change: 1 addition & 0 deletions providers/redfish/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func TestMain(m *testing.M) {
handler.HandleFunc("/redfish/v1/SessionService/Sessions", sessionService)
handler.HandleFunc("/redfish/v1/UpdateService/MultipartUpload", multipartUpload)
handler.HandleFunc("/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs?$expand=*($levels=1)", dellJobs)
handler.HandleFunc("/redfish/v1/TaskService/Tasks/15", openbmcStatus)

return httptest.NewTLSServer(handler)
}()
Expand Down
30 changes: 30 additions & 0 deletions providers/redfish/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package redfish

import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
Expand Down Expand Up @@ -171,3 +172,32 @@ func (c *Conn) dellJobs(state string) ([]*gofishrf.Task, error) {

return tasks, nil
}

func (c *Conn) openbmcGetStatus(jsonstr []byte) (state string, err error) {
type TaskMsg struct {
Message string
}

type TaskStatus struct {
TaskState string
TaskStatus string
Messages []TaskMsg
}

var status TaskStatus

err = json.Unmarshal(jsonstr, &status)
if err != nil {
fmt.Println(err)
} else {
state = strings.ToLower(status.TaskState)
if state != "running" {
// Display all messages when not running (failed or completed)
fmt.Println(status.TaskState, status.TaskStatus)
for _, m := range status.Messages {
fmt.Println(m.Message)
}
}
}
return state, err
}
28 changes: 28 additions & 0 deletions providers/redfish/tasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,31 @@ func Test_dellPurgeScheduledFirmwareInstallJob(t *testing.T) {
t.Fatal(err)
}
}

func Test_openbmcGetStatus(t *testing.T) {
var err error
var state string

// empty (invalid json)
_, err = mockClient.openbmcGetStatus([]byte(""))
if err == nil {
t.Fatal("no error with empty invalid json")
}

// empty valid json
_, err = mockClient.openbmcGetStatus([]byte("{}"))
if err != nil {
t.Fatal(err)
}

// empty valid json
state, err = mockClient.openbmcGetStatus([]byte(
"{\"Id\":\"15\", \"TaskState\": \"TestState\", \"TaskStatus\": \"TestStatus\"}",
))
if err != nil {
t.Fatal(err)
}
if state != "teststate" {
t.Fatal("Wrong test state:", state)
}
}

0 comments on commit c57b61d

Please sign in to comment.