Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GetBootDeviceOverride support for redfish #367

Merged
merged 6 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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)
coffeefreak101 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
coffeefreak101 marked this conversation as resolved.
Show resolved Hide resolved
}

// setBootDevice sets the next boot device.
//
// setPersistent persists the next boot device.
Expand Down Expand Up @@ -78,3 +95,67 @@
}
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{
coffeefreak101 marked this conversation as resolved.
Show resolved Hide resolved
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)
coffeefreak101 marked this conversation as resolved.
Show resolved Hide resolved
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 @@
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")
coffeefreak101 marked this conversation as resolved.
Show resolved Hide resolved
)

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 @@
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 @@

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 {
joelrebel marked this conversation as resolved.
Show resolved Hide resolved
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 @@
"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 @@
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 @@
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