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 5 commits
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: 78 additions & 3 deletions bmc/boot_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,35 @@ 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 string
coffeefreak101 marked this conversation as resolved.
Show resolved Hide resolved
}

// 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 +93,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: "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
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 @@
return users, err
}

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

Check warning on line 461 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L459-L461

Added lines #L459 - L461 were not covered by tests

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

Check warning on line 464 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L463-L464

Added lines #L463 - L464 were not covered by tests

return override, err

Check warning on line 466 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L466

Added line #L466 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) {
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