Skip to content

Commit

Permalink
Add GetBootDeviceOverride support for redfish
Browse files Browse the repository at this point in the history
  • Loading branch information
coffeefreak101 committed Nov 18, 2023
1 parent 4666b85 commit 7002198
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 4 deletions.
81 changes: 81 additions & 0 deletions bmc/boot_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,29 @@ 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
bootOverrider BootDeviceOverrideGetter
}

type BootDeviceOverride struct {
IsPersistent bool
IsEFIBoot bool
Device string
}

// setBootDevice sets the next boot device.
//
// setPersistent persists the next boot device.
Expand Down Expand Up @@ -78,3 +95,67 @@ 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, err error) {
select {
case <-ctx.Done():
err = multierror.Append(err, ctx.Err())
return nil, err

Check warning on line 110 in bmc/boot_device.go

View check run for this annotation

Codecov / codecov/patch

bmc/boot_device.go#L108-L110

Added lines #L108 - L110 were not covered by tests
default:
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, provider.name)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

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

metadata.SuccessfulProvider = provider.name
return override, 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{},
) (*BootDeviceOverride, Metadata, error) {
var err error
metadata := Metadata{
FailedProviderDetail: make(map[string]string),
}

for _, elem := range providers {
provider := &bootOverrideProvider{name: getProviderName(elem)}
switch p := elem.(type) {
case BootDeviceOverrideGetter:
provider.bootOverrider = p
override, getErr := getBootDeviceOverride(ctx, timeout, provider, &metadata)
if getErr != nil || override != nil {
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 nil, metadata, err
}
111 changes: 111 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,112 @@ 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) (override *BootDeviceOverride, err error) {
return m.overrideReturn, m.errReturn
}

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

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
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},
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
override, metadata, err := GetBootDeviceOverrideFromInterface(context.Background(), 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)
})
}
}
6 changes: 6 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,12 @@ func (c *Client) ReadUsers(ctx context.Context) (users []map[string]string, err
return users, err
}

func (c *Client) GetBootDeviceOverride(ctx context.Context) (override *bmc.BootDeviceOverride, err error) {
override, metadata, err := bmc.GetBootDeviceOverrideFromInterface(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
c.setMetadata(metadata)
return override, err

Check warning on line 389 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L386-L389

Added lines #L386 - L389 were not covered by tests
}

Check warning on line 391 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L391

Added line #L391 was not covered by tests
// SetBootDevice pass through to library function
func (c *Client) SetBootDevice(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {
ok, metadata, err := bmc.SetBootDeviceFromInterfaces(ctx, c.perProviderTimeout(ctx), bootDevice, setPersistent, efiBoot, c.registry().GetDriverInterfaces())
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")

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

type ErrUnsupportedHardware struct {
Expand Down
67 changes: 65 additions & 2 deletions internal/redfishwrapper/boot_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package redfishwrapper
import (
"context"

"github.com/bmc-toolbox/bmclib/v2/bmc"
bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
"github.com/pkg/errors"
rf "github.com/stmcginnis/gofish/redfish"
)

// Set the boot device for the system.
func (c *Client) SystemBootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {
// SystemBootDeviceSet set the boot device for the system.
func (c *Client) SystemBootDeviceSet(_ context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {

Check warning on line 13 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L13

Added line #L13 was not covered by tests
if err := c.SessionActive(); err != nil {
return false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())
}
Expand Down Expand Up @@ -76,3 +77,65 @@ func (c *Client) SystemBootDeviceSet(ctx context.Context, bootDevice string, set

return true, nil
}

// bootTargetToDevice tries to convert the redfish boot target to a bmclib supported device string.
// if the target is unknown or unsupported, then a string of the target is returned.
func bootTargetToDevice(target rf.BootSourceOverrideTarget) (device string) {
switch target {
case rf.BiosSetupBootSourceOverrideTarget:
device = "bios"
case rf.CdBootSourceOverrideTarget:
device = "cdrom"
case rf.DiagsBootSourceOverrideTarget:
device = "diag"
case rf.FloppyBootSourceOverrideTarget:
device = "floppy"
case rf.HddBootSourceOverrideTarget:
device = "disk"
case rf.NoneBootSourceOverrideTarget:
device = "none"
case rf.PxeBootSourceOverrideTarget:
device = "pxe"
case rf.RemoteDriveBootSourceOverrideTarget:
device = "remote_drive"
case rf.SDCardBootSourceOverrideTarget:
device = "sd_card"
case rf.UsbBootSourceOverrideTarget:
device = "usb"
case rf.UtilitiesBootSourceOverrideTarget:
device = "utilities"
default:
device = string(target)

Check warning on line 108 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L83-L108

Added lines #L83 - L108 were not covered by tests
}

return device

Check warning on line 111 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L111

Added line #L111 was not covered by tests
}

// GetBootDeviceOverride returns the current boot override settings
func (c *Client) GetBootDeviceOverride(_ context.Context) (*bmc.BootDeviceOverride, error) {
if err := c.SessionActive(); err != nil {
return nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())

Check warning on line 117 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L115-L117

Added lines #L115 - L117 were not covered by tests
}

systems, err := c.client.Service.Systems()
if err != nil {
return nil, err

Check warning on line 122 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L120-L122

Added lines #L120 - L122 were not covered by tests
}

for _, system := range systems {
if system == nil {
continue

Check warning on line 127 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L125-L127

Added lines #L125 - L127 were not covered by tests
}

boot := system.Boot
override := &bmc.BootDeviceOverride{
IsPersistent: boot.BootSourceOverrideEnabled == rf.ContinuousBootSourceOverrideEnabled,
IsEFIBoot: boot.BootSourceOverrideMode == rf.UEFIBootSourceOverrideMode,
Device: bootTargetToDevice(boot.BootSourceOverrideTarget),

Check warning on line 134 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L130-L134

Added lines #L130 - L134 were not covered by tests
}

return override, nil

Check warning on line 137 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L137

Added line #L137 was not covered by tests
}

return nil, bmclibErrs.ErrNoSystemsAvailable

Check warning on line 140 in internal/redfishwrapper/boot_device.go

View check run for this annotation

Codecov / codecov/patch

internal/redfishwrapper/boot_device.go#L140

Added line #L140 was not covered by tests
}
7 changes: 5 additions & 2 deletions providers/redfish/redfish.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/jacobweinstock/registrar"
"github.com/pkg/errors"

"github.com/bmc-toolbox/bmclib/v2/bmc"
bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors"
)

Expand Down Expand Up @@ -180,8 +181,6 @@ func (c *Conn) Compatible(ctx context.Context) bool {
return err == nil
}



// BmcReset power cycles the BMC
func (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {
return c.redfishwrapper.BMCReset(ctx, resetType)
Expand Down Expand Up @@ -215,6 +214,10 @@ func (c *Conn) BootDeviceSet(ctx context.Context, bootDevice string, setPersiste
return c.redfishwrapper.SystemBootDeviceSet(ctx, bootDevice, setPersistent, efiBoot)
}

func (c *Conn) BootDeviceOverrideGet(ctx context.Context) (*bmc.BootDeviceOverride, error) {
return c.redfishwrapper.GetBootDeviceOverride(ctx)

Check warning on line 218 in providers/redfish/redfish.go

View check run for this annotation

Codecov / codecov/patch

providers/redfish/redfish.go#L217-L218

Added lines #L217 - L218 were not covered by tests
}

// SetVirtualMedia sets the virtual media
func (c *Conn) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error) {
return c.redfishwrapper.SetVirtualMedia(ctx, kind, mediaURL)
Expand Down

0 comments on commit 7002198

Please sign in to comment.