[#52] Provide Redfish feedback in RemoteDirect

Currently, errors thrown by RemoteDirect are simply HTTP status codes
from the Redfish client HTTP requests. This change adds feedback to
RemoteDirect errors when the Redfish client provides error feedback.

Closes #52

Change-Id: I4441baf3b98405e92613a2d4e8c44ef63a5d4001
Signed-off-by: Drew Walters <andrew.walters@att.com>
This commit is contained in:
Drew Walters 2020-03-09 22:00:10 +00:00
parent 0cf1068c5f
commit 4ba23313c5
3 changed files with 76 additions and 82 deletions

View File

@ -1,9 +1,12 @@
package redfish
import (
"encoding/json"
"fmt"
"net/http"
redfishClient "opendev.org/airship/go-redfish/client"
aerror "opendev.org/airship/airshipctl/pkg/errors"
)
@ -26,16 +29,29 @@ func (e ErrRedfishMissingConfig) Error() string {
return "missing configuration: " + e.What
}
// Redfish client return error if response has no body.
// In this case this function further checks the
// HTTP response code.
func ScreenRedfishError(httpResp *http.Response, err error) error {
if err != nil && httpResp != nil {
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 400 {
// ScreenRedfishError provides detailed error checking on a Redfish client response.
func ScreenRedfishError(httpResp *http.Response, clientErr error) error {
// NOTE(drewwalters96): clientErr may not be nil even though the request was successful. The HTTP status code
// has to be verified for success on each request. The Redfish client uses HTTP codes 200 and 204 to indicate
// success.
if httpResp != nil && httpResp.StatusCode != 200 && httpResp.StatusCode != 204 {
if clientErr == nil {
return ErrRedfishClient{Message: http.StatusText(httpResp.StatusCode)}
}
oAPIErr, ok := clientErr.(redfishClient.GenericOpenAPIError)
if !ok {
return ErrRedfishClient{Message: "Unable to decode client error."}
}
var resp redfishClient.RedfishError
if err := json.Unmarshal(oAPIErr.Body(), &resp); err != nil {
// No JSON response included; use generic error text.
return ErrRedfishClient{Message: err.Error()}
}
} else if err != nil {
return ErrRedfishClient{Message: err.Error()}
} else if httpResp == nil {
return ErrRedfishClient{Message: "HTTP request failed. Please try again."}
}
return nil
}

View File

@ -27,10 +27,11 @@ func TestRedfishRemoteDirectNormal(t *testing.T) {
defer m.AssertExpectations(t)
systemID := computerSystemID
httpResp := &http.Response{StatusCode: 200}
m.On("GetSystem", context.Background(), systemID).
Return(getTestSystem(), nil, nil)
Return(getTestSystem(), httpResp, nil)
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, httpResp, nil)
systemReq := redfishClient.ComputerSystem{
Boot: redfishClient.Boot{
@ -39,18 +40,18 @@ func TestRedfishRemoteDirectNormal(t *testing.T) {
}
m.On("SetSystem", context.Background(), systemID, systemReq).
Times(1).
Return(redfishClient.ComputerSystem{}, nil, nil)
Return(redfishClient.ComputerSystem{}, httpResp, nil)
resetReq := redfishClient.ResetRequestBody{}
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", context.Background(), systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, httpResp, nil)
resetReq.ResetType = redfishClient.RESETTYPE_ON
m.On("ResetSystem", context.Background(), systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, httpResp, nil)
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
@ -85,9 +86,7 @@ func TestRedfishRemoteDirectGetSystemNetworkError(t *testing.T) {
systemID := computerSystemID
realErr := fmt.Errorf("server request timeout")
httpResp := &http.Response{
StatusCode: 408,
}
httpResp := &http.Response{StatusCode: 408}
m.On("GetSystem", context.Background(), systemID).
Times(1).
Return(redfishClient.ComputerSystem{}, httpResp, realErr)
@ -109,11 +108,8 @@ func TestRedfishRemoteDirectInvalidIsoPath(t *testing.T) {
localRDCfg := rDCfg
localRDCfg.IsoPath = "bogus/path/to.iso"
errStr := "invalid remote boot path"
realErr := fmt.Errorf(errStr)
httpResp := &http.Response{
StatusCode: 500,
}
realErr := redfishClient.GenericOpenAPIError{}
httpResp := &http.Response{StatusCode: 500}
m.On("GetSystem", context.Background(), systemID).
Times(1).
Return(getTestSystem(), nil, nil)
@ -157,19 +153,17 @@ func TestRedfishRemoteDirectSetSystemBootSourceFailed(t *testing.T) {
defer m.AssertExpectations(t)
systemID := computerSystemID
httpSuccResp := &http.Response{StatusCode: 200}
m.On("GetSystem", context.Background(), systemID).
Return(getTestSystem(), nil, nil)
Return(getTestSystem(), httpSuccResp, nil)
m.On("InsertVirtualMedia", context.Background(), "manager-1", "Cd", mock.Anything).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, httpSuccResp, nil)
realErr := fmt.Errorf("unauthorized")
httpResp := &http.Response{
StatusCode: 401,
}
m.On("SetSystem", context.Background(), systemID, mock.Anything).
Times(1).
Return(redfishClient.ComputerSystem{}, httpResp, realErr)
Return(redfishClient.ComputerSystem{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)
@ -184,26 +178,23 @@ func TestRedfishRemoteDirectSystemRebootFailed(t *testing.T) {
defer m.AssertExpectations(t)
systemID := computerSystemID
httpSuccResp := &http.Response{StatusCode: 200}
m.On("GetSystem", context.Background(), systemID).
Return(getTestSystem(), nil, nil)
Return(getTestSystem(), httpSuccResp, nil)
m.On("InsertVirtualMedia", context.Background(), mock.Anything, mock.Anything, mock.Anything).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, httpSuccResp, nil)
m.On("SetSystem", context.Background(), systemID, mock.Anything).
Times(1).
Return(redfishClient.ComputerSystem{}, nil, nil)
Return(redfishClient.ComputerSystem{}, httpSuccResp, nil)
realErr := fmt.Errorf("unauthorized")
httpResp := &http.Response{
StatusCode: 401,
}
resetReq := redfishClient.ResetRequestBody{}
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", context.Background(), systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, httpResp, realErr)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
rDCfg := getDefaultRedfishRemoteDirectObj(t, m)

View File

@ -2,7 +2,6 @@ package redfish
import (
"context"
"fmt"
"net/http"
"testing"
@ -17,47 +16,41 @@ const (
)
func TestRedfishErrorNoError(t *testing.T) {
err := ScreenRedfishError(nil, nil)
err := ScreenRedfishError(&http.Response{StatusCode: 200}, nil)
assert.NoError(t, err)
}
func TestRedfishErrorNonNilErrorWithoutHttpResp(t *testing.T) {
realErr := fmt.Errorf("sample error")
err := ScreenRedfishError(nil, realErr)
assert.Error(t, err)
err := ScreenRedfishError(nil, redfishClient.GenericOpenAPIError{})
_, ok := err.(ErrRedfishClient)
assert.True(t, ok)
}
func TestRedfishErrorNonNilErrorWithHttpRespError(t *testing.T) {
realErr := fmt.Errorf("sample error")
respErr := redfishClient.GenericOpenAPIError{}
httpResp := &http.Response{StatusCode: 408}
err := ScreenRedfishError(httpResp, realErr)
assert.Equal(t, err, ErrRedfishClient{Message: realErr.Error()})
err := ScreenRedfishError(&http.Response{StatusCode: 408}, respErr)
_, ok := err.(ErrRedfishClient)
assert.True(t, ok)
httpResp.StatusCode = 400
err = ScreenRedfishError(httpResp, realErr)
assert.Equal(t, err, ErrRedfishClient{Message: realErr.Error()})
err = ScreenRedfishError(&http.Response{StatusCode: 500}, respErr)
_, ok = err.(ErrRedfishClient)
assert.True(t, ok)
httpResp.StatusCode = 199
err = ScreenRedfishError(httpResp, realErr)
assert.Equal(t, err, ErrRedfishClient{Message: realErr.Error()})
err = ScreenRedfishError(&http.Response{StatusCode: 199}, respErr)
_, ok = err.(ErrRedfishClient)
assert.True(t, ok)
}
func TestRedfishErrorNonNilErrorWithHttpRespOK(t *testing.T) {
realErr := fmt.Errorf("sample error")
respErr := redfishClient.GenericOpenAPIError{}
httpResp := &http.Response{StatusCode: 204}
err := ScreenRedfishError(httpResp, realErr)
// NOTE: Redfish client only uses HTTP 200 & HTTP 204 for success.
err := ScreenRedfishError(&http.Response{StatusCode: 204}, respErr)
assert.NoError(t, err)
httpResp.StatusCode = 200
err = ScreenRedfishError(httpResp, realErr)
assert.NoError(t, err)
httpResp.StatusCode = 399
err = ScreenRedfishError(httpResp, realErr)
err = ScreenRedfishError(&http.Response{StatusCode: 200}, respErr)
assert.NoError(t, err)
}
@ -106,12 +99,12 @@ func TestRedfishUtilRebootSystemOK(t *testing.T) {
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 200}, nil)
resetReq.ResetType = redfishClient.RESETTYPE_ON
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 200}, nil)
err := RebootSystem(ctx, m, systemID)
assert.NoError(t, err)
@ -122,19 +115,17 @@ func TestRedfishUtilRebootSystemForceOffError2(t *testing.T) {
defer m.AssertExpectations(t)
ctx := context.Background()
realErr := fmt.Errorf("unauthorized")
httpResp := &http.Response{
StatusCode: 401,
}
resetReq := redfishClient.ResetRequestBody{}
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, httpResp, realErr)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
err := RebootSystem(ctx, m, systemID)
assert.Error(t, err)
_, ok := err.(ErrRedfishClient)
assert.True(t, ok)
}
func TestRedfishUtilRebootSystemForceOffError(t *testing.T) {
@ -142,18 +133,16 @@ func TestRedfishUtilRebootSystemForceOffError(t *testing.T) {
defer m.AssertExpectations(t)
ctx := context.Background()
realErr := fmt.Errorf("unauthorized")
httpResp := &http.Response{
StatusCode: 401,
}
resetReq := redfishClient.ResetRequestBody{}
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, httpResp, realErr)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
err := RebootSystem(ctx, m, systemID)
assert.Error(t, err)
_, ok := err.(ErrRedfishClient)
assert.True(t, ok)
}
func TestRedfishUtilRebootSystemTurningOnError(t *testing.T) {
@ -165,18 +154,16 @@ func TestRedfishUtilRebootSystemTurningOnError(t *testing.T) {
resetReq.ResetType = redfishClient.RESETTYPE_FORCE_OFF
m.On("ResetSystem", ctx, systemID, resetReq).
Times(1).
Return(redfishClient.RedfishError{}, nil, nil)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 200}, nil)
realErr := fmt.Errorf("unauthorized")
httpResp := &http.Response{
StatusCode: 401,
}
resetOnReq := redfishClient.ResetRequestBody{}
resetOnReq.ResetType = redfishClient.RESETTYPE_ON
m.On("ResetSystem", ctx, systemID, resetOnReq).
Times(1).
Return(redfishClient.RedfishError{}, httpResp, realErr)
Return(redfishClient.RedfishError{}, &http.Response{StatusCode: 401},
redfishClient.GenericOpenAPIError{})
err := RebootSystem(ctx, m, systemID)
assert.Error(t, err)
_, ok := err.(ErrRedfishClient)
assert.True(t, ok)
}