Skip to content

Commit

Permalink
Add wait polling for retrieving redfish storages and systems (#174)
Browse files Browse the repository at this point in the history
* adds wait polling for retrieving redfish storages

* adds polling options to bmcClient

* creates BMCPollingOptions type

* fixes lint errors

* adds bmc polling defaults

* removes ctx error check

* switches back to generic bmc client options struct

* fixes linting error
  • Loading branch information
stefanhipfel authored Nov 19, 2024
1 parent fe41bd7 commit 59bd218
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 158 deletions.
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 17 additions & 13 deletions bmc/bmc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package bmc

import (
"context"

"github.com/stmcginnis/gofish/common"
"github.com/stmcginnis/gofish/redfish"
"k8s.io/apimachinery/pkg/api/resource"
Expand All @@ -12,43 +14,45 @@ import (
// BMC defines an interface for interacting with a Baseboard Management Controller.
type BMC interface {
// PowerOn powers on the system.
PowerOn(systemUUID string) error
PowerOn(ctx context.Context, systemUUID string) error

// PowerOff gracefully shuts down the system.
PowerOff(systemUUID string) error
PowerOff(ctx context.Context, systemUUID string) error

// ForcePowerOff powers off the system.
ForcePowerOff(systemUUID string) error
ForcePowerOff(ctx context.Context, systemUUID string) error

// Reset performs a reset on the system.
Reset(systemUUID string, resetType redfish.ResetType) error
Reset(ctx context.Context, systemUUID string, resetType redfish.ResetType) error

// SetPXEBootOnce sets the boot device for the next system boot.
SetPXEBootOnce(systemUUID string) error
SetPXEBootOnce(ctx context.Context, systemUUID string) error

// GetSystemInfo retrieves information about the system.
GetSystemInfo(systemUUID string) (SystemInfo, error)
GetSystemInfo(ctx context.Context, systemUUID string) (SystemInfo, error)

// Logout closes the BMC client connection by logging out
Logout()

// GetSystems returns the managed systems
GetSystems() ([]Server, error)
GetSystems(ctx context.Context) ([]Server, error)

// GetManager returns the manager
GetManager() (*Manager, error)

GetBootOrder(systemUUID string) ([]string, error)
GetBootOrder(ctx context.Context, systemUUID string) ([]string, error)

GetBiosAttributeValues(ctx context.Context, systemUUID string, attributes []string) (map[string]string, error)

GetBiosAttributeValues(systemUUID string, attributes []string) (map[string]string, error)
SetBiosAttributes(ctx context.Context, systemUUID string, attributes map[string]string) (reset bool, err error)

SetBiosAttributes(systemUUID string, attributes map[string]string) (reset bool, err error)
GetBiosVersion(ctx context.Context, systemUUID string) (string, error)

GetBiosVersion(systemUUID string) (string, error)
SetBootOrder(ctx context.Context, systemUUID string, order []string) error

SetBootOrder(systemUUID string, order []string) error
GetStorages(ctx context.Context, systemUUID string) ([]Storage, error)

GetStorages(systemUUID string) ([]Storage, error)
WaitForServerPowerState(ctx context.Context, systemUUID string, powerState redfish.PowerState) error
}

type Entity struct {
Expand Down
159 changes: 123 additions & 36 deletions bmc/redfish.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,44 @@ import (
"fmt"
"strconv"
"strings"
"time"

"github.com/stmcginnis/gofish"
"github.com/stmcginnis/gofish/redfish"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/wait"
)

var _ BMC = (*RedfishBMC)(nil)

const (
// DefaultResourcePollingInterval is the default interval for polling resources.
DefaultResourcePollingInterval = 30 * time.Second
// DefaultResourcePollingTimeout is the default timeout for polling resources.
DefaultResourcePollingTimeout = 5 * time.Minute
// DefaultPowerPollingInterval is the default interval for polling power state.
DefaultPowerPollingInterval = 30 * time.Second
// DefaultPowerPollingTimeout is the default timeout for polling power state.
DefaultPowerPollingTimeout = 5 * time.Minute
)

// BMCOptions contains the options for the BMC redfish client.
type BMCOptions struct {
Endpoint string
Username string
Password string
BasicAuth bool

ResourcePollingInterval time.Duration
ResourcePollingTimeout time.Duration
PowerPollingInterval time.Duration
PowerPollingTimeout time.Duration
}

// RedfishBMC is an implementation of the BMC interface for Redfish.
type RedfishBMC struct {
client *gofish.APIClient
client *gofish.APIClient
options BMCOptions
}

var pxeBootWithSettingUEFIBootMode = redfish.Boot{
Expand All @@ -35,21 +62,35 @@ var pxeBootWithoutSettingUEFIBootMode = redfish.Boot{
// NewRedfishBMCClient creates a new RedfishBMC with the given connection details.
func NewRedfishBMCClient(
ctx context.Context,
endpoint, username, password string,
basicAuth bool,
options BMCOptions,
) (*RedfishBMC, error) {
clientConfig := gofish.ClientConfig{
Endpoint: endpoint,
Username: username,
Password: password,
Endpoint: options.Endpoint,
Username: options.Username,
Password: options.Password,
Insecure: true,
BasicAuth: basicAuth,
BasicAuth: options.BasicAuth,
}
client, err := gofish.ConnectContext(ctx, clientConfig)
if err != nil {
return nil, fmt.Errorf("failed to connect to redfish endpoint: %w", err)
}
return &RedfishBMC{client: client}, nil
bmc := &RedfishBMC{client: client}
if options.ResourcePollingInterval == 0 {
options.ResourcePollingInterval = DefaultResourcePollingInterval
}
if options.ResourcePollingTimeout == 0 {
options.ResourcePollingTimeout = DefaultResourcePollingTimeout
}
if options.PowerPollingInterval == 0 {
options.PowerPollingInterval = DefaultPowerPollingInterval
}
if options.PowerPollingTimeout == 0 {
options.PowerPollingTimeout = DefaultPowerPollingTimeout
}
bmc.options = options

return bmc, nil
}

// Logout closes the BMC client connection by logging out
Expand All @@ -60,8 +101,8 @@ func (r *RedfishBMC) Logout() {
}

// PowerOn powers on the system using Redfish.
func (r *RedfishBMC) PowerOn(systemUUID string) error {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) PowerOn(ctx context.Context, systemUUID string) error {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return fmt.Errorf("failed to get systems: %w", err)
}
Expand All @@ -76,8 +117,8 @@ func (r *RedfishBMC) PowerOn(systemUUID string) error {
}

// PowerOff gracefully shuts down the system using Redfish.
func (r *RedfishBMC) PowerOff(systemUUID string) error {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) PowerOff(ctx context.Context, systemUUID string) error {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return fmt.Errorf("failed to get systems: %w", err)
}
Expand All @@ -88,8 +129,8 @@ func (r *RedfishBMC) PowerOff(systemUUID string) error {
}

// ForcePowerOff powers off the system using Redfish.
func (r *RedfishBMC) ForcePowerOff(systemUUID string) error {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) ForcePowerOff(ctx context.Context, systemUUID string) error {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return fmt.Errorf("failed to get systems: %w", err)
}
Expand All @@ -100,8 +141,8 @@ func (r *RedfishBMC) ForcePowerOff(systemUUID string) error {
}

// Reset performs a reset on the system using Redfish.
func (r *RedfishBMC) Reset(systemUUID string, resetType redfish.ResetType) error {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) Reset(ctx context.Context, systemUUID string, resetType redfish.ResetType) error {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return fmt.Errorf("failed to get systems: %w", err)
}
Expand All @@ -112,7 +153,7 @@ func (r *RedfishBMC) Reset(systemUUID string, resetType redfish.ResetType) error
}

// GetSystems get managed systems
func (r *RedfishBMC) GetSystems() ([]Server, error) {
func (r *RedfishBMC) GetSystems(ctx context.Context) ([]Server, error) {
service := r.client.GetService()
systems, err := service.Systems()
if err != nil {
Expand All @@ -132,8 +173,8 @@ func (r *RedfishBMC) GetSystems() ([]Server, error) {
}

// SetPXEBootOnce sets the boot device for the next system boot using Redfish.
func (r *RedfishBMC) SetPXEBootOnce(systemUUID string) error {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) SetPXEBootOnce(ctx context.Context, systemUUID string) error {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return fmt.Errorf("failed to get systems: %w", err)
}
Expand Down Expand Up @@ -176,8 +217,8 @@ func (r *RedfishBMC) GetManager() (*Manager, error) {
}

// GetSystemInfo retrieves information about the system using Redfish.
func (r *RedfishBMC) GetSystemInfo(systemUUID string) (SystemInfo, error) {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) GetSystemInfo(ctx context.Context, systemUUID string) (SystemInfo, error) {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return SystemInfo{}, fmt.Errorf("failed to get systems: %w", err)
}
Expand All @@ -200,23 +241,24 @@ func (r *RedfishBMC) GetSystemInfo(systemUUID string) (SystemInfo, error) {
}, nil
}

func (r *RedfishBMC) GetBootOrder(systemUUID string) ([]string, error) {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) GetBootOrder(ctx context.Context, systemUUID string) ([]string, error) {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return []string{}, err
}
return system.Boot.BootOrder, nil
}

func (r *RedfishBMC) GetBiosVersion(systemUUID string) (string, error) {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) GetBiosVersion(ctx context.Context, systemUUID string) (string, error) {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return "", err
}
return system.BIOSVersion, nil
}

func (r *RedfishBMC) GetBiosAttributeValues(
ctx context.Context,
systemUUID string,
attributes []string,
) (
Expand All @@ -226,7 +268,7 @@ func (r *RedfishBMC) GetBiosAttributeValues(
if len(attributes) == 0 {
return
}
system, err := r.getSystemByUUID(systemUUID)
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return
}
Expand All @@ -249,14 +291,15 @@ func (r *RedfishBMC) GetBiosAttributeValues(

// SetBiosAttributes sets given bios attributes. Returns true if bios reset is required
func (r *RedfishBMC) SetBiosAttributes(
ctx context.Context,
systemUUID string,
attributes map[string]string,
) (
reset bool,
err error,
) {
reset = false
system, err := r.getSystemByUUID(systemUUID)
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return
}
Expand All @@ -276,8 +319,8 @@ func (r *RedfishBMC) SetBiosAttributes(
}

// SetBootOrder sets bios boot order
func (r *RedfishBMC) SetBootOrder(systemUUID string, bootOrder []string) error {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) SetBootOrder(ctx context.Context, systemUUID string, bootOrder []string) error {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return err
}
Expand Down Expand Up @@ -349,14 +392,26 @@ func (r *RedfishBMC) checkBiosAttributes(attrs map[string]string) (reset bool, e
return
}

func (r *RedfishBMC) GetStorages(systemUUID string) ([]Storage, error) {
system, err := r.getSystemByUUID(systemUUID)
func (r *RedfishBMC) GetStorages(ctx context.Context, systemUUID string) ([]Storage, error) {
system, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return nil, err
}
systemStorage, err := system.Storage()
var systemStorage []*redfish.Storage
err = wait.PollUntilContextTimeout(
ctx,
r.options.ResourcePollingInterval,
r.options.ResourcePollingTimeout,
true,
func(ctx context.Context) (bool, error) {
systemStorage, err = system.Storage()
if err != nil {
return false, nil
}
return true, nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to wait for for server storages to be ready: %w", err)
}
result := make([]Storage, 0, len(systemStorage))
for _, s := range systemStorage {
Expand Down Expand Up @@ -423,11 +478,21 @@ func (r *RedfishBMC) GetStorages(systemUUID string) ([]Storage, error) {
return result, nil
}

func (r *RedfishBMC) getSystemByUUID(systemUUID string) (*redfish.ComputerSystem, error) {
func (r *RedfishBMC) getSystemByUUID(ctx context.Context, systemUUID string) (*redfish.ComputerSystem, error) {
service := r.client.GetService()
systems, err := service.Systems()
var systems []*redfish.ComputerSystem
err := wait.PollUntilContextTimeout(
ctx,
r.options.ResourcePollingInterval,
r.options.ResourcePollingTimeout,
true,
func(ctx context.Context) (bool, error) {
var err error
systems, err = service.Systems()
return err == nil, nil
})
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to wait for for server systems to be ready: %w", err)
}
for _, system := range systems {
if strings.ToLower(system.UUID) == systemUUID {
Expand All @@ -436,3 +501,25 @@ func (r *RedfishBMC) getSystemByUUID(systemUUID string) (*redfish.ComputerSystem
}
return nil, errors.New("no system found")
}

func (r *RedfishBMC) WaitForServerPowerState(
ctx context.Context,
systemUUID string,
powerState redfish.PowerState,
) error {
if err := wait.PollUntilContextTimeout(
ctx,
r.options.PowerPollingInterval,
r.options.PowerPollingTimeout,
true,
func(ctx context.Context) (done bool, err error) {
sysInfo, err := r.getSystemByUUID(ctx, systemUUID)
if err != nil {
return false, fmt.Errorf("failed to get system info: %w", err)
}
return sysInfo.PowerState == powerState, nil
}); err != nil {
return fmt.Errorf("failed to wait for for server power state: %w", err)
}
return nil
}
Loading

0 comments on commit 59bd218

Please sign in to comment.