Skip to content

Commit 834c495

Browse files
Add GetBootDeviceOverride support for redfish (#367)
Add GetBootDeviceOverride support for redfish
1 parent b496772 commit 834c495

File tree

6 files changed

+356
-32
lines changed

6 files changed

+356
-32
lines changed

bmc/boot_device.go

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,56 @@ import (
99
"github.com/pkg/errors"
1010
)
1111

12+
type BootDeviceType string
13+
14+
const (
15+
BootDeviceTypeBIOS BootDeviceType = "bios"
16+
BootDeviceTypeCDROM BootDeviceType = "cdrom"
17+
BootDeviceTypeDiag BootDeviceType = "diag"
18+
BootDeviceTypeFloppy BootDeviceType = "floppy"
19+
BootDeviceTypeDisk BootDeviceType = "disk"
20+
BootDeviceTypeNone BootDeviceType = "none"
21+
BootDeviceTypePXE BootDeviceType = "pxe"
22+
BootDeviceTypeRemoteDrive BootDeviceType = "remote_drive"
23+
BootDeviceTypeSDCard BootDeviceType = "sd_card"
24+
BootDeviceTypeUSB BootDeviceType = "usb"
25+
BootDeviceTypeUtil BootDeviceType = "utilities"
26+
)
27+
1228
// BootDeviceSetter sets the next boot device for a machine
1329
type BootDeviceSetter interface {
1430
BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error)
1531
}
1632

33+
// BootDeviceOverrideGetter gets boot override settings for a machine
34+
type BootDeviceOverrideGetter interface {
35+
BootDeviceOverrideGet(ctx context.Context) (override BootDeviceOverride, err error)
36+
}
37+
1738
// bootDeviceProviders is an internal struct to correlate an implementation/provider and its name
1839
type bootDeviceProviders struct {
1940
name string
2041
bootDeviceSetter BootDeviceSetter
2142
}
2243

44+
// bootOverrideProvider is an internal struct to correlate an implementation/provider and its name
45+
type bootOverrideProvider struct {
46+
name string
47+
bootOverrideGetter BootDeviceOverrideGetter
48+
}
49+
50+
type BootDeviceOverride struct {
51+
IsPersistent bool
52+
IsEFIBoot bool
53+
Device BootDeviceType
54+
}
55+
2356
// setBootDevice sets the next boot device.
2457
//
2558
// setPersistent persists the next boot device.
2659
// efiBoot sets up the device to boot off UEFI instead of legacy.
2760
func setBootDevice(ctx context.Context, timeout time.Duration, bootDevice string, setPersistent, efiBoot bool, b []bootDeviceProviders) (ok bool, metadata Metadata, err error) {
28-
metadataLocal := Metadata{
29-
FailedProviderDetail: make(map[string]string),
30-
}
61+
metadataLocal := newMetadata()
3162

3263
for _, elem := range b {
3364
if elem.bootDeviceSetter == nil {
@@ -78,3 +109,63 @@ func SetBootDeviceFromInterfaces(ctx context.Context, timeout time.Duration, boo
78109
}
79110
return setBootDevice(ctx, timeout, bootDevice, setPersistent, efiBoot, bdSetters)
80111
}
112+
113+
// getBootDeviceOverride gets the boot device override settings for the given provider,
114+
// and updates the given metadata with provider attempts and errors.
115+
func getBootDeviceOverride(
116+
ctx context.Context,
117+
timeout time.Duration,
118+
provider *bootOverrideProvider,
119+
metadata *Metadata,
120+
) (override BootDeviceOverride, ok bool, err error) {
121+
select {
122+
case <-ctx.Done():
123+
err = multierror.Append(err, ctx.Err())
124+
return override, ok, err
125+
default:
126+
metadata.ProvidersAttempted = append(metadata.ProvidersAttempted, provider.name)
127+
ctx, cancel := context.WithTimeout(ctx, timeout)
128+
defer cancel()
129+
130+
override, err = provider.bootOverrideGetter.BootDeviceOverrideGet(ctx)
131+
if err != nil {
132+
metadata.FailedProviderDetail[provider.name] = err.Error()
133+
return override, ok, nil
134+
}
135+
136+
metadata.SuccessfulProvider = provider.name
137+
return override, true, nil
138+
}
139+
}
140+
141+
// GetBootDeviceOverrideFromInterface will get boot device override settings from the first successful
142+
// call to a BootDeviceOverrideGetter in the array of providers.
143+
func GetBootDeviceOverrideFromInterface(
144+
ctx context.Context,
145+
timeout time.Duration,
146+
providers []interface{},
147+
) (override BootDeviceOverride, metadata Metadata, err error) {
148+
metadata = newMetadata()
149+
150+
for _, elem := range providers {
151+
switch p := elem.(type) {
152+
case BootDeviceOverrideGetter:
153+
provider := &bootOverrideProvider{name: getProviderName(elem), bootOverrideGetter: p}
154+
override, ok, getErr := getBootDeviceOverride(ctx, timeout, provider, &metadata)
155+
if getErr != nil || ok {
156+
return override, metadata, getErr
157+
}
158+
default:
159+
e := fmt.Errorf("not a BootDeviceOverrideGetter implementation: %T", p)
160+
err = multierror.Append(err, e)
161+
}
162+
}
163+
164+
if len(metadata.ProvidersAttempted) == 0 {
165+
err = multierror.Append(err, errors.New("no BootDeviceOverrideGetter implementations found"))
166+
} else {
167+
err = multierror.Append(err, errors.New("failed to get boot device override settings"))
168+
}
169+
170+
return override, metadata, err
171+
}

bmc/boot_device_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"testing"
77
"time"
88

9+
"fmt"
910
"github.com/google/go-cmp/cmp"
1011
"github.com/hashicorp/go-multierror"
12+
"github.com/stretchr/testify/assert"
1113
)
1214

1315
type bootDeviceTester struct {
@@ -117,3 +119,129 @@ func TestSetBootDeviceFromInterfaces(t *testing.T) {
117119
})
118120
}
119121
}
122+
123+
type mockBootDeviceOverrideGetter struct {
124+
overrideReturn BootDeviceOverride
125+
errReturn error
126+
}
127+
128+
func (m *mockBootDeviceOverrideGetter) Name() string {
129+
return "Mock"
130+
}
131+
132+
func (m *mockBootDeviceOverrideGetter) BootDeviceOverrideGet(_ context.Context) (BootDeviceOverride, error) {
133+
return m.overrideReturn, m.errReturn
134+
}
135+
136+
func TestBootDeviceOverrideGet(t *testing.T) {
137+
successOverride := BootDeviceOverride{
138+
IsPersistent: false,
139+
IsEFIBoot: true,
140+
Device: BootDeviceTypeDisk,
141+
}
142+
143+
successMetadata := &Metadata{
144+
SuccessfulProvider: "Mock",
145+
ProvidersAttempted: []string{"Mock"},
146+
SuccessfulOpenConns: nil,
147+
SuccessfulCloseConns: []string(nil),
148+
FailedProviderDetail: map[string]string{},
149+
}
150+
151+
mixedMetadata := &Metadata{
152+
SuccessfulProvider: "Mock",
153+
ProvidersAttempted: []string{"Mock", "Mock"},
154+
SuccessfulOpenConns: nil,
155+
SuccessfulCloseConns: []string(nil),
156+
FailedProviderDetail: map[string]string{"Mock": "foo-failure"},
157+
}
158+
159+
failMetadata := &Metadata{
160+
SuccessfulProvider: "",
161+
ProvidersAttempted: []string{"Mock"},
162+
SuccessfulOpenConns: nil,
163+
SuccessfulCloseConns: []string(nil),
164+
FailedProviderDetail: map[string]string{"Mock": "foo-failure"},
165+
}
166+
167+
emptyMetadata := &Metadata{
168+
FailedProviderDetail: make(map[string]string),
169+
}
170+
171+
testCases := []struct {
172+
name string
173+
hasCanceledContext bool
174+
expectedErrorMsg string
175+
expectedMetadata *Metadata
176+
expectedOverride BootDeviceOverride
177+
getters []interface{}
178+
}{
179+
{
180+
name: "success",
181+
expectedMetadata: successMetadata,
182+
expectedOverride: successOverride,
183+
getters: []interface{}{
184+
&mockBootDeviceOverrideGetter{overrideReturn: successOverride},
185+
},
186+
},
187+
{
188+
name: "multiple getters",
189+
expectedMetadata: mixedMetadata,
190+
expectedOverride: successOverride,
191+
getters: []interface{}{
192+
"not a getter",
193+
&mockBootDeviceOverrideGetter{errReturn: fmt.Errorf("foo-failure")},
194+
&mockBootDeviceOverrideGetter{overrideReturn: successOverride},
195+
},
196+
},
197+
{
198+
name: "error",
199+
expectedMetadata: failMetadata,
200+
expectedErrorMsg: "failed to get boot device override settings",
201+
getters: []interface{}{
202+
&mockBootDeviceOverrideGetter{errReturn: fmt.Errorf("foo-failure")},
203+
},
204+
},
205+
{
206+
name: "nil BootDeviceOverrideGetters",
207+
expectedMetadata: emptyMetadata,
208+
expectedErrorMsg: "no BootDeviceOverrideGetter implementations found",
209+
},
210+
{
211+
name: "nil BootDeviceOverrideGetter",
212+
expectedMetadata: emptyMetadata,
213+
expectedErrorMsg: "no BootDeviceOverrideGetter implementations found",
214+
getters: []interface{}{nil},
215+
},
216+
{
217+
name: "with canceled context",
218+
hasCanceledContext: true,
219+
expectedMetadata: emptyMetadata,
220+
expectedErrorMsg: "context canceled",
221+
getters: []interface{}{
222+
&mockBootDeviceOverrideGetter{},
223+
},
224+
},
225+
}
226+
227+
for _, testCase := range testCases {
228+
t.Run(testCase.name, func(t *testing.T) {
229+
ctx, cancel := context.WithCancel(context.Background())
230+
defer cancel()
231+
232+
if testCase.hasCanceledContext {
233+
cancel()
234+
}
235+
236+
override, metadata, err := GetBootDeviceOverrideFromInterface(ctx, 0, testCase.getters)
237+
238+
if testCase.expectedErrorMsg != "" {
239+
assert.ErrorContains(t, err, testCase.expectedErrorMsg)
240+
} else {
241+
assert.Nil(t, err)
242+
}
243+
assert.Equal(t, testCase.expectedOverride, override)
244+
assert.Equal(t, testCase.expectedMetadata, &metadata)
245+
})
246+
}
247+
}

client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,17 @@ func (c *Client) ReadUsers(ctx context.Context) (users []map[string]string, err
455455
return users, err
456456
}
457457

458+
// GetBootDeviceOverride pass through to library function
459+
func (c *Client) GetBootDeviceOverride(ctx context.Context) (override bmc.BootDeviceOverride, err error) {
460+
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "GetBootDeviceOverride")
461+
defer span.End()
462+
463+
override, metadata, err := bmc.GetBootDeviceOverrideFromInterface(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())
464+
c.setMetadata(metadata)
465+
466+
return override, err
467+
}
468+
458469
// SetBootDevice pass through to library function
459470
func (c *Client) SetBootDevice(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {
460471
ctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, "SetBootDevice")

errors/errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ var (
117117

118118
// ErrSystemVendorModel is returned when the system vendor, model attributes could not be identified.
119119
ErrSystemVendorModel = errors.New("error identifying system vendor, model attributes")
120+
121+
// ErrRedfishNoSystems is returned when the API of the device provides and empty array of systems.
122+
ErrRedfishNoSystems = errors.New("redfish: no Systems were found on the device")
120123
)
121124

122125
type ErrUnsupportedHardware struct {

0 commit comments

Comments
 (0)