Skip to content

Commit

Permalink
Add GetBootDeviceOverride support for redfish (#367)
Browse files Browse the repository at this point in the history
Add GetBootDeviceOverride support for redfish
  • Loading branch information
coffeefreak101 authored Nov 22, 2023
1 parent b496772 commit 834c495
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 32 deletions.
97 changes: 94 additions & 3 deletions bmc/boot_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,56 @@ import (
"github.com/pkg/errors"
)

type BootDeviceType string

const (
BootDeviceTypeBIOS BootDeviceType = "bios"
BootDeviceTypeCDROM BootDeviceType = "cdrom"
BootDeviceTypeDiag BootDeviceType = "diag"
BootDeviceTypeFloppy BootDeviceType = "floppy"
BootDeviceTypeDisk BootDeviceType = "disk"
BootDeviceTypeNone BootDeviceType = "none"
BootDeviceTypePXE BootDeviceType = "pxe"
BootDeviceTypeRemoteDrive BootDeviceType = "remote_drive"
BootDeviceTypeSDCard BootDeviceType = "sd_card"
BootDeviceTypeUSB BootDeviceType = "usb"
BootDeviceTypeUtil BootDeviceType = "utilities"
)

// BootDeviceSetter sets the next boot device for a machine
type BootDeviceSetter interface {
BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error)
}

// BootDeviceOverrideGetter gets boot override settings for a machine
type BootDeviceOverrideGetter interface {
BootDeviceOverrideGet(ctx context.Context) (override BootDeviceOverride, err error)
}

// bootDeviceProviders is an internal struct to correlate an implementation/provider and its name
type bootDeviceProviders struct {
name string
bootDeviceSetter BootDeviceSetter
}

// bootOverrideProvider is an internal struct to correlate an implementation/provider and its name
type bootOverrideProvider struct {
name string
bootOverrideGetter BootDeviceOverrideGetter
}

type BootDeviceOverride struct {
IsPersistent bool
IsEFIBoot bool
Device BootDeviceType
}

// setBootDevice sets the next boot device.
//
// setPersistent persists the next boot device.
// efiBoot sets up the device to boot off UEFI instead of legacy.
func setBootDevice(ctx context.Context, timeout time.Duration, bootDevice string, setPersistent, efiBoot bool, b []bootDeviceProviders) (ok bool, metadata Metadata, err error) {
metadataLocal := Metadata{
FailedProviderDetail: make(map[string]string),
}
metadataLocal := newMetadata()

for _, elem := range b {
if elem.bootDeviceSetter == nil {
Expand Down Expand Up @@ -78,3 +109,63 @@ func SetBootDeviceFromInterfaces(ctx context.Context, timeout time.Duration, boo
}
return setBootDevice(ctx, timeout, bootDevice, setPersistent, efiBoot, bdSetters)
}

// getBootDeviceOverride gets the boot device override settings for the given provider,
// and updates the given metadata with provider attempts and errors.
func getBootDeviceOverride(
ctx context.Context,
timeout time.Duration,
provider *bootOverrideProvider,
metadata *Metadata,
) (override BootDeviceOverride, ok bool, err error) {
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())
return override, ok, err
default:
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, provider.name)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

override, err = provider.bootOverrideGetter.BootDeviceOverrideGet(ctx)
if err != nil {
metadata.FailedProviderDetail[provider.name] = err.Error()
return override, ok, nil
}

metadata.SuccessfulProvider = provider.name
return override, true, nil
}
}

// GetBootDeviceOverrideFromInterface will get boot device override settings from the first successful
// call to a BootDeviceOverrideGetter in the array of providers.
func GetBootDeviceOverrideFromInterface(
ctx context.Context,
timeout time.Duration,
providers []interface{},
) (override BootDeviceOverride, metadata Metadata, err error) {
metadata = newMetadata()

for _, elem := range providers {
switch p := elem.(type) {
case BootDeviceOverrideGetter:
provider := &bootOverrideProvider{name: getProviderName(elem), bootOverrideGetter: p}
override, ok, getErr := getBootDeviceOverride(ctx, timeout, provider, &metadata)
if getErr != nil || ok {
return override, metadata, getErr
}
default:
e := fmt.Errorf("not a BootDeviceOverrideGetter implementation: %T", p)
err = multierror.Append(err, e)
}
}

if len(metadata.ProvidersAttempted) == 0 {
err = multierror.Append(err, errors.New("no BootDeviceOverrideGetter implementations found"))
} else {
err = multierror.Append(err, errors.New("failed to get boot device override settings"))
}

return override, metadata, err
}
128 changes: 128 additions & 0 deletions bmc/boot_device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"testing"
"time"

"fmt"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-multierror"
"github.com/stretchr/testify/assert"
)

type bootDeviceTester struct {
Expand Down Expand Up @@ -117,3 +119,129 @@ func TestSetBootDeviceFromInterfaces(t *testing.T) {
})
}
}

type mockBootDeviceOverrideGetter struct {
overrideReturn BootDeviceOverride
errReturn error
}

func (m *mockBootDeviceOverrideGetter) Name() string {
return "Mock"
}

func (m *mockBootDeviceOverrideGetter) BootDeviceOverrideGet(_ context.Context) (BootDeviceOverride, error) {
return m.overrideReturn, m.errReturn
}

func TestBootDeviceOverrideGet(t *testing.T) {
successOverride := BootDeviceOverride{
IsPersistent: false,
IsEFIBoot: true,
Device: BootDeviceTypeDisk,
}

successMetadata := &Metadata{
SuccessfulProvider: "Mock",
ProvidersAttempted: []string{"Mock"},
SuccessfulOpenConns: nil,
SuccessfulCloseConns: []string(nil),
FailedProviderDetail: map[string]string{},
}

mixedMetadata := &Metadata{
SuccessfulProvider: "Mock",
ProvidersAttempted: []string{"Mock", "Mock"},
SuccessfulOpenConns: nil,
SuccessfulCloseConns: []string(nil),
FailedProviderDetail: map[string]string{"Mock": "foo-failure"},
}

failMetadata := &Metadata{
SuccessfulProvider: "",
ProvidersAttempted: []string{"Mock"},
SuccessfulOpenConns: nil,
SuccessfulCloseConns: []string(nil),
FailedProviderDetail: map[string]string{"Mock": "foo-failure"},
}

emptyMetadata := &Metadata{
FailedProviderDetail: make(map[string]string),
}

testCases := []struct {
name string
hasCanceledContext bool
expectedErrorMsg string
expectedMetadata *Metadata
expectedOverride BootDeviceOverride
getters []interface{}
}{
{
name: "success",
expectedMetadata: successMetadata,
expectedOverride: successOverride,
getters: []interface{}{
&mockBootDeviceOverrideGetter{overrideReturn: successOverride},
},
},
{
name: "multiple getters",
expectedMetadata: mixedMetadata,
expectedOverride: successOverride,
getters: []interface{}{
"not a getter",
&mockBootDeviceOverrideGetter{errReturn: fmt.Errorf("foo-failure")},
&mockBootDeviceOverrideGetter{overrideReturn: successOverride},
},
},
{
name: "error",
expectedMetadata: failMetadata,
expectedErrorMsg: "failed to get boot device override settings",
getters: []interface{}{
&mockBootDeviceOverrideGetter{errReturn: fmt.Errorf("foo-failure")},
},
},
{
name: "nil BootDeviceOverrideGetters",
expectedMetadata: emptyMetadata,
expectedErrorMsg: "no BootDeviceOverrideGetter implementations found",
},
{
name: "nil BootDeviceOverrideGetter",
expectedMetadata: emptyMetadata,
expectedErrorMsg: "no BootDeviceOverrideGetter implementations found",
getters: []interface{}{nil},
},
{
name: "with canceled context",
hasCanceledContext: true,
expectedMetadata: emptyMetadata,
expectedErrorMsg: "context canceled",
getters: []interface{}{
&mockBootDeviceOverrideGetter{},
},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

if testCase.hasCanceledContext {
cancel()
}

override, metadata, err := GetBootDeviceOverrideFromInterface(ctx, 0, testCase.getters)

if testCase.expectedErrorMsg != "" {
assert.ErrorContains(t, err, testCase.expectedErrorMsg)
} else {
assert.Nil(t, err)
}
assert.Equal(t, testCase.expectedOverride, override)
assert.Equal(t, testCase.expectedMetadata, &metadata)
})
}
}
11 changes: 11 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,17 @@ func (c *Client) ReadUsers(ctx context.Context) (users []map[string]string, err
return users, err
}

// GetBootDeviceOverride pass through to library function
func (c *Client) GetBootDeviceOverride(ctx context.Context) (override bmc.BootDeviceOverride, err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "GetBootDeviceOverride")
defer span.End()

override, metadata, err := bmc.GetBootDeviceOverrideFromInterface(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)

return override, err
}

// SetBootDevice pass through to library function
func (c *Client) SetBootDevice(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "SetBootDevice")
Expand Down
3 changes: 3 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ var (

// ErrSystemVendorModel is returned when the system vendor, model attributes could not be identified.
ErrSystemVendorModel = errors.New("error identifying system vendor, model attributes")

// ErrRedfishNoSystems is returned when the API of the device provides and empty array of systems.
ErrRedfishNoSystems = errors.New("redfish: no Systems were found on the device")
)

type ErrUnsupportedHardware struct {
Expand Down
Loading

0 comments on commit 834c495

Please sign in to comment.