From f48584a91d876240d2ed362c0b0e5a4d79b2cf15 Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Fri, 8 Nov 2024 12:22:15 +0100 Subject: [PATCH 1/7] adds wait polling for retrieving redfish storages --- bmc/bmc.go | 7 +++- bmc/redfish.go | 42 +++++++++++++++++++++-- internal/controller/server_controller.go | 43 ++++++++++++------------ 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/bmc/bmc.go b/bmc/bmc.go index c787682..9b647c2 100644 --- a/bmc/bmc.go +++ b/bmc/bmc.go @@ -4,6 +4,9 @@ package bmc import ( + "context" + "time" + "github.com/stmcginnis/gofish/common" "github.com/stmcginnis/gofish/redfish" ) @@ -47,7 +50,9 @@ type BMC interface { SetBootOrder(systemUUID string, order []string) error - GetStorages(systemUUID string) ([]Storage, error) + GetStorages(ctx context.Context, systemUUID string) ([]Storage, error) + + WaitForServerPowerState(ctx context.Context, systemUUID string, interval, timeout time.Duration, powerState redfish.PowerState) error } type Entity struct { diff --git a/bmc/redfish.go b/bmc/redfish.go index 1590083..981b3d8 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -9,9 +9,11 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" + "k8s.io/apimachinery/pkg/util/wait" ) var _ BMC = (*RedfishBMC)(nil) @@ -342,14 +344,29 @@ func (r *RedfishBMC) checkBiosAttributes(attrs map[string]string) (reset bool, e return } -func (r *RedfishBMC) GetStorages(systemUUID string) ([]Storage, error) { +func (r *RedfishBMC) GetStorages(ctx context.Context, systemUUID string) ([]Storage, error) { system, err := r.getSystemByUUID(systemUUID) if err != nil { return nil, err } - systemStorage, err := system.Storage() + var systemStorage []*redfish.Storage + err = wait.PollUntilContextTimeout( + ctx, + 10*time.Second, + 5*time.Minute, + true, + func(ctx context.Context) (bool, error) { + if ctx.Err() != nil { + return false, ctx.Err() + } + systemStorage, err = system.Storage() + if err != nil { + return false, nil + } + return len(systemStorage) > 0, 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 { @@ -429,3 +446,22 @@ func (r *RedfishBMC) getSystemByUUID(systemUUID string) (*redfish.ComputerSystem } return nil, errors.New("no system found") } + +func (r *RedfishBMC) WaitForServerPowerState( + ctx context.Context, + systemUUID string, + interval, + timeout time.Duration, + powerState redfish.PowerState, +) error { + if err := wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (done bool, err error) { + sysInfo, err := r.getSystemByUUID(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 +} diff --git a/internal/controller/server_controller.go b/internal/controller/server_controller.go index b753b6c..8899f2b 100644 --- a/internal/controller/server_controller.go +++ b/internal/controller/server_controller.go @@ -12,9 +12,6 @@ import ( "sort" "time" - "github.com/ironcore-dev/metal-operator/bmc" - "k8s.io/apimachinery/pkg/util/wait" - "github.com/go-logr/logr" "github.com/ironcore-dev/controller-utils/clientutils" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" @@ -279,7 +276,7 @@ func (r *ServerReconciler) handleDiscoveryState(ctx context.Context, log logr.Lo if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } - storages, err := bmcClient.GetStorages(server.Spec.UUID) + storages, err := bmcClient.GetStorages(ctx, server.Spec.UUID) if err != nil { return false, fmt.Errorf("failed to get storages for Server: %w", err) } @@ -722,7 +719,12 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. if err := bmcClient.PowerOn(server.Spec.UUID); err != nil { return fmt.Errorf("failed to power on server: %w", err) } - if err := r.waitForServerPowerState(ctx, log, bmcClient, server, redfish.OnPowerState); err != nil { + if err := bmcClient.WaitForServerPowerState( + ctx, server.Spec.UUID, + r.PowerPollingInterval, + r.PowerPollingTimeout, + redfish.OnPowerState, + ); err != nil { return fmt.Errorf("failed to wait for server power on server: %w", err) } case powerOpOff: @@ -731,14 +733,26 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. if err := powerOffType(server.Spec.UUID); err != nil { return fmt.Errorf("failed to power off server: %w", err) } - if err := r.waitForServerPowerState(ctx, log, bmcClient, server, redfish.OffPowerState); err != nil { + if err := bmcClient.WaitForServerPowerState( + ctx, + server.Spec.UUID, + r.PowerPollingInterval, + r.PowerPollingTimeout, + redfish.OffPowerState, + ); err != nil { if r.EnforcePowerOff { log.V(1).Info("Failed to wait for server graceful shutdown, retrying with force power off") powerOffType = bmcClient.ForcePowerOff if err := powerOffType(server.Spec.UUID); err != nil { return fmt.Errorf("failed to power off server: %w", err) } - if err := r.waitForServerPowerState(ctx, log, bmcClient, server, redfish.OffPowerState); err != nil { + if err := bmcClient.WaitForServerPowerState( + ctx, + server.Spec.UUID, + r.PowerPollingInterval, + r.PowerPollingTimeout, + redfish.OffPowerState, + ); err != nil { return fmt.Errorf("failed to wait for server force power off: %w", err) } } else { @@ -751,21 +765,6 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. return nil } -func (r *ServerReconciler) waitForServerPowerState(ctx context.Context, log logr.Logger, bmcClient bmc.BMC, server *metalv1alpha1.Server, powerState redfish.PowerState) error { - if err := wait.PollUntilContextTimeout(ctx, r.PowerPollingInterval, r.PowerPollingTimeout, true, func(ctx context.Context) (done bool, err error) { - log.V(1).Info("Waiting for Server to reach target power state", "TargetPowerState", powerState) - sysInfo, err := bmcClient.GetSystemInfo(server.Spec.UUID) - if err != nil { - return false, fmt.Errorf("failed to get system info: %w", err) - } - log.V(1).Info("Read Server power state", "PowerState", sysInfo.PowerState, "TargetPowerState", powerState) - return sysInfo.PowerState == powerState, nil - }); err != nil { - return fmt.Errorf("failed to wait for for server power state: %w", err) - } - return nil -} - func (r *ServerReconciler) ensureIndicatorLED(ctx context.Context, log logr.Logger, server *metalv1alpha1.Server) error { // TODO: implement return nil From c7bf55023862dfad777d8fc1f49019b6e6d30b59 Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Fri, 8 Nov 2024 17:11:00 +0100 Subject: [PATCH 2/7] adds polling options to bmcClient --- bmc/bmc.go | 27 ++--- bmc/redfish.go | 132 ++++++++++++++------- bmc/redfish_kube.go | 9 +- bmc/redfish_local.go | 13 +- cmd/manager/main.go | 56 +++++---- internal/controller/bmc_controller.go | 11 +- internal/controller/bmcutils.go | 54 ++++++--- internal/controller/endpoint_controller.go | 19 +-- internal/controller/server_controller.go | 60 ++++------ internal/controller/suite_test.go | 8 +- 10 files changed, 229 insertions(+), 160 deletions(-) diff --git a/bmc/bmc.go b/bmc/bmc.go index 9b647c2..988defc 100644 --- a/bmc/bmc.go +++ b/bmc/bmc.go @@ -5,7 +5,6 @@ package bmc import ( "context" - "time" "github.com/stmcginnis/gofish/common" "github.com/stmcginnis/gofish/redfish" @@ -14,45 +13,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(systemUUID string, attributes []string) (map[string]string, error) + GetBiosAttributeValues(ctx context.Context, systemUUID string, attributes []string) (map[string]string, error) - SetBiosAttributes(systemUUID string, attributes map[string]string) (reset bool, err error) + SetBiosAttributes(ctx context.Context, systemUUID string, attributes map[string]string) (reset bool, err error) - GetBiosVersion(systemUUID string) (string, error) + GetBiosVersion(ctx context.Context, systemUUID string) (string, error) - SetBootOrder(systemUUID string, order []string) error + SetBootOrder(ctx context.Context, systemUUID string, order []string) error GetStorages(ctx context.Context, systemUUID string) ([]Storage, error) - WaitForServerPowerState(ctx context.Context, systemUUID string, interval, timeout time.Duration, powerState redfish.PowerState) error + WaitForServerPowerState(ctx context.Context, systemUUID string, powerState redfish.PowerState) error } type Entity struct { diff --git a/bmc/redfish.go b/bmc/redfish.go index 981b3d8..31412be 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -18,9 +18,21 @@ import ( var _ BMC = (*RedfishBMC)(nil) +type Options struct { + Endpoint, Username, 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 + + options Options } var pxeBootWithSettingUEFIBootMode = redfish.Boot{ @@ -36,21 +48,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 Options, ) (*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 = 5 * time.Second + } + if options.ResourcePollingTimeout == 0 { + options.ResourcePollingTimeout = 5 * time.Minute + } + if options.PowerPollingInterval == 0 { + options.PowerPollingInterval = 5 * time.Second + } + if options.PowerPollingTimeout == 0 { + options.PowerPollingTimeout = 5 * time.Minute + } + bmc.options = options + + return bmc, nil } // Logout closes the BMC client connection by logging out @@ -61,8 +87,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) } @@ -77,8 +103,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) } @@ -89,8 +115,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) } @@ -101,8 +127,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) } @@ -113,7 +139,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 { @@ -133,8 +159,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) } @@ -177,8 +203,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) } @@ -195,16 +221,16 @@ 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 } @@ -212,6 +238,7 @@ func (r *RedfishBMC) GetBiosVersion(systemUUID string) (string, error) { } func (r *RedfishBMC) GetBiosAttributeValues( + ctx context.Context, systemUUID string, attributes []string, ) ( @@ -221,7 +248,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 } @@ -244,6 +271,7 @@ 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, ) ( @@ -251,7 +279,7 @@ func (r *RedfishBMC) SetBiosAttributes( err error, ) { reset = false - system, err := r.getSystemByUUID(systemUUID) + system, err := r.getSystemByUUID(ctx, systemUUID) if err != nil { return } @@ -271,8 +299,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 } @@ -345,15 +373,15 @@ func (r *RedfishBMC) checkBiosAttributes(attrs map[string]string) (reset bool, e } func (r *RedfishBMC) GetStorages(ctx context.Context, systemUUID string) ([]Storage, error) { - system, err := r.getSystemByUUID(systemUUID) + system, err := r.getSystemByUUID(ctx, systemUUID) if err != nil { return nil, err } var systemStorage []*redfish.Storage err = wait.PollUntilContextTimeout( ctx, - 10*time.Second, - 5*time.Minute, + r.options.ResourcePollingInterval, + r.options.ResourcePollingTimeout, true, func(ctx context.Context) (bool, error) { if ctx.Err() != nil { @@ -363,7 +391,7 @@ func (r *RedfishBMC) GetStorages(ctx context.Context, systemUUID string) ([]Stor if err != nil { return false, nil } - return len(systemStorage) > 0, nil + return true, nil }) if err != nil { return nil, fmt.Errorf("failed to wait for for server storages to be ready: %w", err) @@ -433,11 +461,24 @@ func (r *RedfishBMC) GetStorages(ctx context.Context, systemUUID string) ([]Stor 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) { + if ctx.Err() != nil { + return false, ctx.Err() + } + 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 { @@ -450,17 +491,20 @@ func (r *RedfishBMC) getSystemByUUID(systemUUID string) (*redfish.ComputerSystem func (r *RedfishBMC) WaitForServerPowerState( ctx context.Context, systemUUID string, - interval, - timeout time.Duration, powerState redfish.PowerState, ) error { - if err := wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (done bool, err error) { - sysInfo, err := r.getSystemByUUID(systemUUID) - if err != nil { - return false, fmt.Errorf("failed to get system info: %w", err) - } - return sysInfo.PowerState == powerState, nil - }); err != nil { + 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 diff --git a/bmc/redfish_kube.go b/bmc/redfish_kube.go index 2e31f7e..111a04c 100644 --- a/bmc/redfish_kube.go +++ b/bmc/redfish_kube.go @@ -37,12 +37,11 @@ type RedfishKubeBMC struct { // NewRedfishKubeBMCClient creates a new RedfishKubeBMC with the given connection details. func NewRedfishKubeBMCClient( ctx context.Context, - endpoint, username, password string, - basicAuth bool, + bmcOptions Options, c client.Client, ns string, ) (BMC, error) { - bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth) + bmc, err := NewRedfishBMCClient(ctx, bmcOptions) if err != nil { return nil, err } @@ -58,8 +57,8 @@ func NewRedfishKubeBMCClient( } // SetPXEBootOnce sets the boot device for the next system boot using Redfish. -func (r *RedfishKubeBMC) SetPXEBootOnce(systemUUID string) error { - system, err := r.getSystemByUUID(systemUUID) +func (r *RedfishKubeBMC) 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) } diff --git a/bmc/redfish_local.go b/bmc/redfish_local.go index da9a6bb..ad7cd15 100644 --- a/bmc/redfish_local.go +++ b/bmc/redfish_local.go @@ -20,18 +20,17 @@ type RedfishLocalBMC struct { // NewRedfishLocalBMCClient creates a new RedfishLocalBMC with the given connection details. func NewRedfishLocalBMCClient( ctx context.Context, - endpoint, username, password string, - basicAuth bool, + options Options, ) (BMC, error) { - bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth) + bmc, err := NewRedfishBMCClient(ctx, options) if err != nil { return nil, err } return &RedfishLocalBMC{RedfishBMC: bmc}, nil } -func (r RedfishLocalBMC) PowerOn(systemUUID string) error { - system, err := r.getSystemByUUID(systemUUID) +func (r RedfishLocalBMC) PowerOn(ctx context.Context, systemUUID string) error { + system, err := r.getSystemByUUID(ctx, systemUUID) if err != nil { return fmt.Errorf("failed to get system: %w", err) } @@ -43,8 +42,8 @@ func (r RedfishLocalBMC) PowerOn(systemUUID string) error { return nil } -func (r RedfishLocalBMC) PowerOff(systemUUID string) error { - system, err := r.getSystemByUUID(systemUUID) +func (r RedfishLocalBMC) PowerOff(ctx context.Context, systemUUID string) error { + system, err := r.getSystemByUUID(ctx, systemUUID) if err != nil { return fmt.Errorf("failed to get system: %w", err) } diff --git a/cmd/manager/main.go b/cmd/manager/main.go index dd8ab3e..4dc2128 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -44,30 +44,34 @@ func init() { func main() { var ( - metricsAddr string - enableLeaderElection bool - probeAddr string - secureMetrics bool - enableHTTP2 bool - macPrefixesFile string - insecure bool - managerNamespace string - probeImage string - probeOSImage string - registryPort int - registryProtocol string - registryURL string - registryResyncInterval time.Duration - webhookPort int - enforceFirstBoot bool - enforcePowerOff bool - serverResyncInterval time.Duration - powerPollingInterval time.Duration - powerPollingTimeout time.Duration - discoveryTimeout time.Duration + metricsAddr string + enableLeaderElection bool + probeAddr string + secureMetrics bool + enableHTTP2 bool + macPrefixesFile string + insecure bool + managerNamespace string + probeImage string + probeOSImage string + registryPort int + registryProtocol string + registryURL string + registryResyncInterval time.Duration + webhookPort int + enforceFirstBoot bool + enforcePowerOff bool + serverResyncInterval time.Duration + powerPollingInterval time.Duration + powerPollingTimeout time.Duration + resourcePollingInterval time.Duration + resourcePollingTimeout time.Duration + discoveryTimeout time.Duration ) flag.DurationVar(&discoveryTimeout, "discovery-timeout", 30*time.Minute, "Timeout for discovery boot") + flag.DurationVar(&resourcePollingInterval, "resource-polling-interval", 5*time.Second, "Interval between polling resources") + flag.DurationVar(&resourcePollingTimeout, "resource-polling-timeout", 2*time.Minute, "Timeout for polling resources") flag.DurationVar(&powerPollingInterval, "power-polling-interval", 5*time.Second, "Interval between polling power state") flag.DurationVar(&powerPollingTimeout, "power-polling-timeout", 2*time.Minute, "Timeout for polling power state") @@ -220,9 +224,13 @@ func main() { ResyncInterval: serverResyncInterval, EnforceFirstBoot: enforceFirstBoot, EnforcePowerOff: enforcePowerOff, - PowerPollingInterval: powerPollingInterval, - PowerPollingTimeout: powerPollingTimeout, - DiscoveryTimeout: discoveryTimeout, + PollingOptionsBMC: controller.PollingOptionsBMC{ + PowerPollingInterval: powerPollingInterval, + PowerPollingTimeout: powerPollingTimeout, + ResourcePollingInterval: resourcePollingInterval, + ResourcePollingTimeout: resourcePollingTimeout, + }, + DiscoveryTimeout: discoveryTimeout, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Server") os.Exit(1) diff --git a/internal/controller/bmc_controller.go b/internal/controller/bmc_controller.go index 30b9797..376347f 100644 --- a/internal/controller/bmc_controller.go +++ b/internal/controller/bmc_controller.go @@ -26,8 +26,9 @@ const BMCFinalizer = "metal.ironcore.dev/bmc" // BMCReconciler reconciles a BMC object type BMCReconciler struct { client.Client - Scheme *runtime.Scheme - Insecure bool + Scheme *runtime.Scheme + Insecure bool + PollingOptionsBMC PollingOptionsBMC } //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=endpoints,verbs=get;list;watch @@ -117,7 +118,7 @@ func (r *BMCReconciler) updateBMCStatusDetails(ctx context.Context, log logr.Log return fmt.Errorf("failed to patch IP and MAC address status: %w", err) } - bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure) + bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.PollingOptionsBMC) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -147,13 +148,13 @@ func (r *BMCReconciler) updateBMCStatusDetails(ctx context.Context, log logr.Log } func (r *BMCReconciler) discoverServers(ctx context.Context, log logr.Logger, bmcObj *metalv1alpha1.BMC) error { - bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure) + bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.PollingOptionsBMC) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } defer bmcClient.Logout() - servers, err := bmcClient.GetSystems() + servers, err := bmcClient.GetSystems(ctx) if err != nil { return fmt.Errorf("failed to get Servers from BMC: %w", err) } diff --git a/internal/controller/bmcutils.go b/internal/controller/bmcutils.go index 72500b8..c569c3d 100644 --- a/internal/controller/bmcutils.go +++ b/internal/controller/bmcutils.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "net" + "time" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" "github.com/ironcore-dev/metal-operator/bmc" @@ -15,7 +16,14 @@ import ( const DefaultKubeNamespace = "default" -func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool) (bmc.BMC, error) { +type PollingOptionsBMC struct { + PowerPollingInterval time.Duration + PowerPollingTimeout time.Duration + ResourcePollingInterval time.Duration + ResourcePollingTimeout time.Duration +} + +func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool, polling PollingOptionsBMC) (bmc.BMC, error) { if server.Spec.BMCRef != nil { b := &metalv1alpha1.BMC{} bmcName := server.Spec.BMCRef.Name @@ -23,7 +31,7 @@ func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1 return nil, fmt.Errorf("failed to get BMC: %w", err) } - return GetBMCClientFromBMC(ctx, c, b, insecure) + return GetBMCClientFromBMC(ctx, c, b, insecure, polling) } if server.Spec.BMC != nil { @@ -40,13 +48,14 @@ func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1 server.Spec.BMC.Address, server.Spec.BMC.Protocol.Port, bmcSecret, + polling, ) } return nil, fmt.Errorf("server %s has neither a BMCRef nor a BMC configured", server.Name) } -func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC, insecure bool) (bmc.BMC, error) { +func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC, insecure bool, polling PollingOptionsBMC) (bmc.BMC, error) { var address string if bmcObj.Spec.EndpointRef != nil { @@ -66,44 +75,61 @@ func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1al return nil, fmt.Errorf("failed to get BMC secret: %w", err) } - return CreateBMCClient(ctx, c, insecure, bmcObj.Spec.Protocol.Name, address, bmcObj.Spec.Protocol.Port, bmcSecret) + return CreateBMCClient(ctx, c, insecure, bmcObj.Spec.Protocol.Name, address, bmcObj.Spec.Protocol.Port, bmcSecret, polling) } -func CreateBMCClient(ctx context.Context, c client.Client, insecure bool, bmcProtocol metalv1alpha1.ProtocolName, address string, port int32, bmcSecret *metalv1alpha1.BMCSecret) (bmc.BMC, error) { +func CreateBMCClient( + ctx context.Context, + c client.Client, + insecure bool, + bmcProtocol metalv1alpha1.ProtocolName, + address string, + port int32, + bmcSecret *metalv1alpha1.BMCSecret, + polling PollingOptionsBMC, +) (bmc.BMC, error) { protocol := "https" if insecure { protocol = "http" } var bmcClient bmc.BMC + var err error + options := bmc.Options{ + BasicAuth: true, + PowerPollingInterval: polling.PowerPollingInterval, + PowerPollingTimeout: polling.PowerPollingTimeout, + ResourcePollingInterval: polling.ResourcePollingInterval, + ResourcePollingTimeout: polling.ResourcePollingTimeout, + } switch bmcProtocol { case metalv1alpha1.ProtocolRedfish: - bmcAddress := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - username, password, err := GetBMCCredentialsFromSecret(bmcSecret) + options.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + options.Username, options.Password, err = GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishBMCClient(ctx, bmcAddress, username, password, true) + bmcClient, err = bmc.NewRedfishBMCClient(ctx, options) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } case metalv1alpha1.ProtocolRedfishLocal: - bmcAddress := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - username, password, err := GetBMCCredentialsFromSecret(bmcSecret) + options.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + options.Username, options.Password, err = GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishLocalBMCClient(ctx, bmcAddress, username, password, true) + bmcClient, err = bmc.NewRedfishLocalBMCClient(ctx, options) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } case metalv1alpha1.ProtocolRedfishKube: - bmcAddress := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - username, password, err := GetBMCCredentialsFromSecret(bmcSecret) + options.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + options.Username, options.Password, err = GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishKubeBMCClient(ctx, bmcAddress, username, password, true, c, DefaultKubeNamespace) + bmcClient, err = bmc.NewRedfishKubeBMCClient(ctx, options, c, DefaultKubeNamespace) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } diff --git a/internal/controller/endpoint_controller.go b/internal/controller/endpoint_controller.go index d4adc3f..5ae7265 100644 --- a/internal/controller/endpoint_controller.go +++ b/internal/controller/endpoint_controller.go @@ -81,12 +81,17 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end if len(m.DefaultCredentials) == 0 { return ctrl.Result{}, fmt.Errorf("no default credentials present for BMC %s", endpoint.Spec.MACAddress) } + options := bmc.Options{ + BasicAuth: true, + Username: m.DefaultCredentials[0].Username, + Password: m.DefaultCredentials[0].Password, + } switch m.Protocol { case metalv1alpha1.ProtocolRedfish: log.V(1).Info("Creating client for BMC") - bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - log.V(1).Info("Creating client for BMC", "Address", bmcAddress) - bmcClient, err := bmc.NewRedfishBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true) + options.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + log.V(1).Info("Creating client for BMC", "Address", options.Endpoint) + bmcClient, err := bmc.NewRedfishBMCClient(ctx, options) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } @@ -106,8 +111,8 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end log.V(1).Info("Applied BMC object for endpoint") case metalv1alpha1.ProtocolRedfishLocal: log.V(1).Info("Creating client for a local test BMC") - bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - bmcClient, err := bmc.NewRedfishLocalBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true) + options.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + bmcClient, err := bmc.NewRedfishLocalBMCClient(ctx, options) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } @@ -125,8 +130,8 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end log.V(1).Info("Applied BMC object for Endpoint") case metalv1alpha1.ProtocolRedfishKube: log.V(1).Info("Creating client for a kube test BMC") - bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - bmcClient, err := bmc.NewRedfishKubeBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true, r.Client, DefaultKubeNamespace) + options.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + bmcClient, err := bmc.NewRedfishKubeBMCClient(ctx, options, r.Client, DefaultKubeNamespace) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } diff --git a/internal/controller/server_controller.go b/internal/controller/server_controller.go index 8899f2b..3ac362f 100644 --- a/internal/controller/server_controller.go +++ b/internal/controller/server_controller.go @@ -61,8 +61,7 @@ type ServerReconciler struct { EnforceFirstBoot bool EnforcePowerOff bool ResyncInterval time.Duration - PowerPollingInterval time.Duration - PowerPollingTimeout time.Duration + PollingOptionsBMC PollingOptionsBMC DiscoveryTimeout time.Duration } @@ -272,7 +271,7 @@ func (r *ServerReconciler) handleDiscoveryState(ctx context.Context, log logr.Lo } log.V(1).Info("Server state set to power on") - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } @@ -357,6 +356,7 @@ func (r *ServerReconciler) handleAvailableState(ctx context.Context, log logr.Lo } log.V(1).Info("Server state set to power off") } + log.V(1).Info("ensureInitialBootConfigurationIsDeleted") if err := r.ensureInitialBootConfigurationIsDeleted(ctx, server); err != nil { return false, fmt.Errorf("failed to ensure server initial boot configuration is deleted: %w", err) } @@ -415,13 +415,13 @@ func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logg log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } defer bmcClient.Logout() - systemInfo, err := bmcClient.GetSystemInfo(server.Spec.UUID) + systemInfo, err := bmcClient.GetSystemInfo(ctx, server.Spec.UUID) if err != nil { return fmt.Errorf("failed to get system info for Server: %w", err) } @@ -434,7 +434,7 @@ func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logg server.Status.Model = systemInfo.Model server.Status.IndicatorLED = metalv1alpha1.IndicatorLED(systemInfo.IndicatorLED) - currentBiosVersion, err := bmcClient.GetBiosVersion(server.Spec.UUID) + currentBiosVersion, err := bmcClient.GetBiosVersion(ctx, server.Spec.UUID) if err != nil { return fmt.Errorf("failed to load bios version: %w", err) } @@ -446,7 +446,7 @@ func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logg for k := range bios.Settings { keys = append(keys, k) } - attributes, err := bmcClient.GetBiosAttributeValues(server.Spec.UUID, keys) + attributes, err := bmcClient.GetBiosAttributeValues(ctx, server.Spec.UUID, keys) if err != nil { return fmt.Errorf("failed load bios settings: %w", err) } @@ -613,7 +613,7 @@ func (r *ServerReconciler) pxeBootServer(ctx context.Context, log logr.Logger, s return fmt.Errorf("can only PXE boot server with valid BMC ref or inline BMC configuration") } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) defer func() { if bmcClient != nil { bmcClient.Logout() @@ -623,7 +623,7 @@ func (r *ServerReconciler) pxeBootServer(ctx context.Context, log logr.Logger, s if err != nil { return fmt.Errorf("failed to get BMC client: %w", err) } - if err := bmcClient.SetPXEBootOnce(server.Spec.UUID); err != nil { + if err := bmcClient.SetPXEBootOnce(ctx, server.Spec.UUID); err != nil { return fmt.Errorf("failed to set PXE boot one for server: %w", err) } return nil @@ -704,7 +704,7 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) defer func() { if bmcClient != nil { bmcClient.Logout() @@ -716,13 +716,11 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. switch powerOp { case powerOpOn: - if err := bmcClient.PowerOn(server.Spec.UUID); err != nil { + if err := bmcClient.PowerOn(ctx, server.Spec.UUID); err != nil { return fmt.Errorf("failed to power on server: %w", err) } if err := bmcClient.WaitForServerPowerState( ctx, server.Spec.UUID, - r.PowerPollingInterval, - r.PowerPollingTimeout, redfish.OnPowerState, ); err != nil { return fmt.Errorf("failed to wait for server power on server: %w", err) @@ -730,29 +728,17 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. case powerOpOff: powerOffType := bmcClient.PowerOff - if err := powerOffType(server.Spec.UUID); err != nil { + if err := powerOffType(ctx, server.Spec.UUID); err != nil { return fmt.Errorf("failed to power off server: %w", err) } - if err := bmcClient.WaitForServerPowerState( - ctx, - server.Spec.UUID, - r.PowerPollingInterval, - r.PowerPollingTimeout, - redfish.OffPowerState, - ); err != nil { + if err := bmcClient.WaitForServerPowerState(ctx, server.Spec.UUID, redfish.OffPowerState); err != nil { if r.EnforcePowerOff { log.V(1).Info("Failed to wait for server graceful shutdown, retrying with force power off") powerOffType = bmcClient.ForcePowerOff - if err := powerOffType(server.Spec.UUID); err != nil { + if err := powerOffType(ctx, server.Spec.UUID); err != nil { return fmt.Errorf("failed to power off server: %w", err) } - if err := bmcClient.WaitForServerPowerState( - ctx, - server.Spec.UUID, - r.PowerPollingInterval, - r.PowerPollingTimeout, - redfish.OffPowerState, - ); err != nil { + if err := bmcClient.WaitForServerPowerState(ctx, server.Spec.UUID, redfish.OffPowerState); err != nil { return fmt.Errorf("failed to wait for server force power off: %w", err) } } else { @@ -828,13 +814,13 @@ func (r *ServerReconciler) applyBootOrder(ctx context.Context, log logr.Logger, log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } defer bmcClient.Logout() - order, err := bmcClient.GetBootOrder(server.Spec.UUID) + order, err := bmcClient.GetBootOrder(ctx, server.Spec.UUID) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -851,7 +837,7 @@ func (r *ServerReconciler) applyBootOrder(ctx context.Context, log logr.Logger, } } if change { - return bmcClient.SetBootOrder(server.Spec.UUID, newOrder) + return bmcClient.SetBootOrder(ctx, server.Spec.UUID, newOrder) } return nil } @@ -862,13 +848,13 @@ func (r *ServerReconciler) applyBiosSettings(ctx context.Context, log logr.Logge log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } defer bmcClient.Logout() - version, err := bmcClient.GetBiosVersion(server.Spec.UUID) + version, err := bmcClient.GetBiosVersion(ctx, server.Spec.UUID) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -885,7 +871,7 @@ func (r *ServerReconciler) applyBiosSettings(ctx context.Context, log logr.Logge } } } - reset, err := bmcClient.SetBiosAttributes(server.Spec.UUID, diff) + reset, err := bmcClient.SetBiosAttributes(ctx, server.Spec.UUID, diff) if err != nil { return err } @@ -914,13 +900,13 @@ func (r *ServerReconciler) handleAnnotionOperations(ctx context.Context, log log if !ok { return false, nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } defer bmcClient.Logout() log.V(1).Info("Handling operation", "Operation", operation) - if err := bmcClient.Reset(server.Spec.UUID, redfish.ResetType(operation)); err != nil { + if err := bmcClient.Reset(ctx, server.Spec.UUID, redfish.ResetType(operation)); err != nil { return false, fmt.Errorf("failed to reset server: %w", err) } log.V(1).Info("Operation completed", "Operation", operation) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 76e36e9..c4ea011 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -181,9 +181,11 @@ func SetupTest() *corev1.Namespace { RegistryResyncInterval: 50 * time.Millisecond, ResyncInterval: 100 * time.Millisecond, EnforceFirstBoot: true, - PowerPollingInterval: 50 * time.Millisecond, - PowerPollingTimeout: 200 * time.Millisecond, - DiscoveryTimeout: 500 * time.Millisecond, // Force timeout to be quick for tests + PollingOptionsBMC: PollingOptionsBMC{ + PowerPollingInterval: 50 * time.Millisecond, + PowerPollingTimeout: 200 * time.Millisecond, + }, + DiscoveryTimeout: 500 * time.Millisecond, // Force timeout to be quick for tests }).SetupWithManager(k8sManager)).To(Succeed()) Expect((&ServerClaimReconciler{ From 6781b302b38cb0804ba9f9c6333e395938eea715 Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Fri, 8 Nov 2024 17:36:16 +0100 Subject: [PATCH 3/7] creates BMCPollingOptions type --- api/v1alpha1/zz_generated.deepcopy.go | 2 +- bmc/redfish.go | 25 +++++++------- bmc/redfish_kube.go | 6 ++-- bmc/redfish_local.go | 6 ++-- cmd/manager/main.go | 3 +- internal/controller/bmc_controller.go | 7 ++-- internal/controller/bmcutils.go | 40 +++++++--------------- internal/controller/endpoint_controller.go | 33 ++++++++++-------- internal/controller/server_controller.go | 17 ++++----- internal/controller/suite_test.go | 3 +- 10 files changed, 69 insertions(+), 73 deletions(-) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 2ac9af0..28b19ad 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -8,7 +8,7 @@ package v1alpha1 import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/bmc/redfish.go b/bmc/redfish.go index 31412be..334261b 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -18,10 +18,7 @@ import ( var _ BMC = (*RedfishBMC)(nil) -type Options struct { - Endpoint, Username, Password string - BasicAuth bool - +type PollingOptions struct { ResourcePollingInterval time.Duration ResourcePollingTimeout time.Duration PowerPollingInterval time.Duration @@ -30,9 +27,10 @@ type Options struct { // RedfishBMC is an implementation of the BMC interface for Redfish. type RedfishBMC struct { - client *gofish.APIClient - - options Options + client *gofish.APIClient + endpoint, username, password string + basicAuth bool + options PollingOptions } var pxeBootWithSettingUEFIBootMode = redfish.Boot{ @@ -48,14 +46,17 @@ var pxeBootWithoutSettingUEFIBootMode = redfish.Boot{ // NewRedfishBMCClient creates a new RedfishBMC with the given connection details. func NewRedfishBMCClient( ctx context.Context, - options Options, + endpoint, username, password string, + basicAuth bool, + + options PollingOptions, ) (*RedfishBMC, error) { clientConfig := gofish.ClientConfig{ - Endpoint: options.Endpoint, - Username: options.Username, - Password: options.Password, + Endpoint: endpoint, + Username: username, + Password: password, Insecure: true, - BasicAuth: options.BasicAuth, + BasicAuth: basicAuth, } client, err := gofish.ConnectContext(ctx, clientConfig) if err != nil { diff --git a/bmc/redfish_kube.go b/bmc/redfish_kube.go index 111a04c..9613867 100644 --- a/bmc/redfish_kube.go +++ b/bmc/redfish_kube.go @@ -37,11 +37,13 @@ type RedfishKubeBMC struct { // NewRedfishKubeBMCClient creates a new RedfishKubeBMC with the given connection details. func NewRedfishKubeBMCClient( ctx context.Context, - bmcOptions Options, + endpoint, username, password string, + basicAuth bool, + options PollingOptions, c client.Client, ns string, ) (BMC, error) { - bmc, err := NewRedfishBMCClient(ctx, bmcOptions) + bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth, options) if err != nil { return nil, err } diff --git a/bmc/redfish_local.go b/bmc/redfish_local.go index ad7cd15..d3f026a 100644 --- a/bmc/redfish_local.go +++ b/bmc/redfish_local.go @@ -20,9 +20,11 @@ type RedfishLocalBMC struct { // NewRedfishLocalBMCClient creates a new RedfishLocalBMC with the given connection details. func NewRedfishLocalBMCClient( ctx context.Context, - options Options, + endpoint, username, password string, + basicAuth bool, + options PollingOptions, ) (BMC, error) { - bmc, err := NewRedfishBMCClient(ctx, options) + bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth, options) if err != nil { return nil, err } diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 4dc2128..42c8509 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -14,6 +14,7 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" + "github.com/ironcore-dev/metal-operator/bmc" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -224,7 +225,7 @@ func main() { ResyncInterval: serverResyncInterval, EnforceFirstBoot: enforceFirstBoot, EnforcePowerOff: enforcePowerOff, - PollingOptionsBMC: controller.PollingOptionsBMC{ + BMCPollingOptions: bmc.PollingOptions{ PowerPollingInterval: powerPollingInterval, PowerPollingTimeout: powerPollingTimeout, ResourcePollingInterval: resourcePollingInterval, diff --git a/internal/controller/bmc_controller.go b/internal/controller/bmc_controller.go index 376347f..6e6506a 100644 --- a/internal/controller/bmc_controller.go +++ b/internal/controller/bmc_controller.go @@ -13,6 +13,7 @@ import ( "github.com/go-logr/logr" "github.com/ironcore-dev/controller-utils/clientutils" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" + "github.com/ironcore-dev/metal-operator/bmc" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -28,7 +29,7 @@ type BMCReconciler struct { client.Client Scheme *runtime.Scheme Insecure bool - PollingOptionsBMC PollingOptionsBMC + BMCPollingOptions bmc.PollingOptions } //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=endpoints,verbs=get;list;watch @@ -118,7 +119,7 @@ func (r *BMCReconciler) updateBMCStatusDetails(ctx context.Context, log logr.Log return fmt.Errorf("failed to patch IP and MAC address status: %w", err) } - bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.BMCPollingOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -148,7 +149,7 @@ func (r *BMCReconciler) updateBMCStatusDetails(ctx context.Context, log logr.Log } func (r *BMCReconciler) discoverServers(ctx context.Context, log logr.Logger, bmcObj *metalv1alpha1.BMC) error { - bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientFromBMC(ctx, r.Client, bmcObj, r.Insecure, r.BMCPollingOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } diff --git a/internal/controller/bmcutils.go b/internal/controller/bmcutils.go index c569c3d..1c44651 100644 --- a/internal/controller/bmcutils.go +++ b/internal/controller/bmcutils.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "net" - "time" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" "github.com/ironcore-dev/metal-operator/bmc" @@ -16,14 +15,7 @@ import ( const DefaultKubeNamespace = "default" -type PollingOptionsBMC struct { - PowerPollingInterval time.Duration - PowerPollingTimeout time.Duration - ResourcePollingInterval time.Duration - ResourcePollingTimeout time.Duration -} - -func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool, polling PollingOptionsBMC) (bmc.BMC, error) { +func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool, polling bmc.PollingOptions) (bmc.BMC, error) { if server.Spec.BMCRef != nil { b := &metalv1alpha1.BMC{} bmcName := server.Spec.BMCRef.Name @@ -55,7 +47,7 @@ func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1 return nil, fmt.Errorf("server %s has neither a BMCRef nor a BMC configured", server.Name) } -func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC, insecure bool, polling PollingOptionsBMC) (bmc.BMC, error) { +func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC, insecure bool, polling bmc.PollingOptions) (bmc.BMC, error) { var address string if bmcObj.Spec.EndpointRef != nil { @@ -86,7 +78,7 @@ func CreateBMCClient( address string, port int32, bmcSecret *metalv1alpha1.BMCSecret, - polling PollingOptionsBMC, + polling bmc.PollingOptions, ) (bmc.BMC, error) { protocol := "https" if insecure { @@ -94,42 +86,34 @@ func CreateBMCClient( } var bmcClient bmc.BMC - var err error - options := bmc.Options{ - BasicAuth: true, - PowerPollingInterval: polling.PowerPollingInterval, - PowerPollingTimeout: polling.PowerPollingTimeout, - ResourcePollingInterval: polling.ResourcePollingInterval, - ResourcePollingTimeout: polling.ResourcePollingTimeout, - } switch bmcProtocol { case metalv1alpha1.ProtocolRedfish: - options.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - options.Username, options.Password, err = GetBMCCredentialsFromSecret(bmcSecret) + endpoint := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + username, password, err := GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishBMCClient(ctx, options) + bmcClient, err = bmc.NewRedfishBMCClient(ctx, endpoint, username, password, true, polling) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } case metalv1alpha1.ProtocolRedfishLocal: - options.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - options.Username, options.Password, err = GetBMCCredentialsFromSecret(bmcSecret) + endpoint := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + username, password, err := GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishLocalBMCClient(ctx, options) + bmcClient, err = bmc.NewRedfishLocalBMCClient(ctx, endpoint, username, password, true, polling) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } case metalv1alpha1.ProtocolRedfishKube: - options.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - options.Username, options.Password, err = GetBMCCredentialsFromSecret(bmcSecret) + endpoint := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + username, password, err := GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishKubeBMCClient(ctx, options, c, DefaultKubeNamespace) + bmcClient, err = bmc.NewRedfishKubeBMCClient(ctx, endpoint, username, password, true, polling, c, DefaultKubeNamespace) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } diff --git a/internal/controller/endpoint_controller.go b/internal/controller/endpoint_controller.go index 5ae7265..0590bbb 100644 --- a/internal/controller/endpoint_controller.go +++ b/internal/controller/endpoint_controller.go @@ -29,9 +29,10 @@ const ( // EndpointReconciler reconciles a Endpoints object type EndpointReconciler struct { client.Client - Scheme *runtime.Scheme - MACPrefixes *macdb.MacPrefixes - Insecure bool + Scheme *runtime.Scheme + MACPrefixes *macdb.MacPrefixes + Insecure bool + BMCPollingOptions bmc.PollingOptions } //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=bmcs,verbs=get;list;watch;create;update;patch;delete @@ -81,17 +82,12 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end if len(m.DefaultCredentials) == 0 { return ctrl.Result{}, fmt.Errorf("no default credentials present for BMC %s", endpoint.Spec.MACAddress) } - options := bmc.Options{ - BasicAuth: true, - Username: m.DefaultCredentials[0].Username, - Password: m.DefaultCredentials[0].Password, - } switch m.Protocol { case metalv1alpha1.ProtocolRedfish: log.V(1).Info("Creating client for BMC") - options.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - log.V(1).Info("Creating client for BMC", "Address", options.Endpoint) - bmcClient, err := bmc.NewRedfishBMCClient(ctx, options) + bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + log.V(1).Info("Creating client for BMC", "Address", bmcAddress) + bmcClient, err := bmc.NewRedfishBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true, r.BMCPollingOptions) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } @@ -111,8 +107,8 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end log.V(1).Info("Applied BMC object for endpoint") case metalv1alpha1.ProtocolRedfishLocal: log.V(1).Info("Creating client for a local test BMC") - options.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - bmcClient, err := bmc.NewRedfishLocalBMCClient(ctx, options) + bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + bmcClient, err := bmc.NewRedfishLocalBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true, r.BMCPollingOptions) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } @@ -130,8 +126,15 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end log.V(1).Info("Applied BMC object for Endpoint") case metalv1alpha1.ProtocolRedfishKube: log.V(1).Info("Creating client for a kube test BMC") - options.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - bmcClient, err := bmc.NewRedfishKubeBMCClient(ctx, options, r.Client, DefaultKubeNamespace) + bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + bmcClient, err := bmc.NewRedfishKubeBMCClient( + ctx, + bmcAddress, + m.DefaultCredentials[0].Username, + m.DefaultCredentials[0].Password, + true, + r.BMCPollingOptions, + r.Client, DefaultKubeNamespace) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } diff --git a/internal/controller/server_controller.go b/internal/controller/server_controller.go index 3ac362f..7c8d7ed 100644 --- a/internal/controller/server_controller.go +++ b/internal/controller/server_controller.go @@ -15,6 +15,7 @@ import ( "github.com/go-logr/logr" "github.com/ironcore-dev/controller-utils/clientutils" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" + "github.com/ironcore-dev/metal-operator/bmc" "github.com/ironcore-dev/metal-operator/internal/api/registry" "github.com/ironcore-dev/metal-operator/internal/ignition" "github.com/stmcginnis/gofish/redfish" @@ -61,7 +62,7 @@ type ServerReconciler struct { EnforceFirstBoot bool EnforcePowerOff bool ResyncInterval time.Duration - PollingOptionsBMC PollingOptionsBMC + BMCPollingOptions bmc.PollingOptions DiscoveryTimeout time.Duration } @@ -271,7 +272,7 @@ func (r *ServerReconciler) handleDiscoveryState(ctx context.Context, log logr.Lo } log.V(1).Info("Server state set to power on") - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } @@ -415,7 +416,7 @@ func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logg log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -613,7 +614,7 @@ func (r *ServerReconciler) pxeBootServer(ctx context.Context, log logr.Logger, s return fmt.Errorf("can only PXE boot server with valid BMC ref or inline BMC configuration") } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) defer func() { if bmcClient != nil { bmcClient.Logout() @@ -704,7 +705,7 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) defer func() { if bmcClient != nil { bmcClient.Logout() @@ -814,7 +815,7 @@ func (r *ServerReconciler) applyBootOrder(ctx context.Context, log logr.Logger, log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -848,7 +849,7 @@ func (r *ServerReconciler) applyBiosSettings(ctx context.Context, log logr.Logge log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -900,7 +901,7 @@ func (r *ServerReconciler) handleAnnotionOperations(ctx context.Context, log log if !ok { return false, nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.PollingOptionsBMC) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index c4ea011..4cbb314 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -12,6 +12,7 @@ import ( "time" metalv1alpha1 "github.com/ironcore-dev/metal-operator/api/v1alpha1" + "github.com/ironcore-dev/metal-operator/bmc" "github.com/ironcore-dev/metal-operator/internal/api/macdb" "github.com/ironcore-dev/metal-operator/internal/registry" . "github.com/onsi/ginkgo/v2" @@ -181,7 +182,7 @@ func SetupTest() *corev1.Namespace { RegistryResyncInterval: 50 * time.Millisecond, ResyncInterval: 100 * time.Millisecond, EnforceFirstBoot: true, - PollingOptionsBMC: PollingOptionsBMC{ + BMCPollingOptions: bmc.PollingOptions{ PowerPollingInterval: 50 * time.Millisecond, PowerPollingTimeout: 200 * time.Millisecond, }, From 73a8e65d1951a87405046e443caa07a8ff15ebce Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Mon, 11 Nov 2024 14:23:03 +0100 Subject: [PATCH 4/7] fixes lint errors --- bmc/redfish.go | 6 ++---- cmd/manager/main.go | 3 ++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/bmc/redfish.go b/bmc/redfish.go index 334261b..18bab34 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -27,10 +27,8 @@ type PollingOptions struct { // RedfishBMC is an implementation of the BMC interface for Redfish. type RedfishBMC struct { - client *gofish.APIClient - endpoint, username, password string - basicAuth bool - options PollingOptions + client *gofish.APIClient + options PollingOptions } var pxeBootWithSettingUEFIBootMode = redfish.Boot{ diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 42c8509..49f6424 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -71,7 +71,8 @@ func main() { ) flag.DurationVar(&discoveryTimeout, "discovery-timeout", 30*time.Minute, "Timeout for discovery boot") - flag.DurationVar(&resourcePollingInterval, "resource-polling-interval", 5*time.Second, "Interval between polling resources") + flag.DurationVar(&resourcePollingInterval, "resource-polling-interval", 5*time.Second, + "Interval between polling resources") flag.DurationVar(&resourcePollingTimeout, "resource-polling-timeout", 2*time.Minute, "Timeout for polling resources") flag.DurationVar(&powerPollingInterval, "power-polling-interval", 5*time.Second, "Interval between polling power state") From 7d6697daf3c3ab41b8f632ed302eaf2ffa80ce80 Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Tue, 12 Nov 2024 14:17:50 +0100 Subject: [PATCH 5/7] adds bmc polling defaults --- bmc/redfish.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bmc/redfish.go b/bmc/redfish.go index 18bab34..3fbc682 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -18,6 +18,18 @@ import ( 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 +) + +// PollingOptions contains the options for polling server resources type PollingOptions struct { ResourcePollingInterval time.Duration ResourcePollingTimeout time.Duration @@ -62,16 +74,16 @@ func NewRedfishBMCClient( } bmc := &RedfishBMC{client: client} if options.ResourcePollingInterval == 0 { - options.ResourcePollingInterval = 5 * time.Second + options.ResourcePollingInterval = DefaultResourcePollingInterval } if options.ResourcePollingTimeout == 0 { - options.ResourcePollingTimeout = 5 * time.Minute + options.ResourcePollingTimeout = DefaultResourcePollingTimeout } if options.PowerPollingInterval == 0 { - options.PowerPollingInterval = 5 * time.Second + options.PowerPollingInterval = DefaultPowerPollingInterval } if options.PowerPollingTimeout == 0 { - options.PowerPollingTimeout = 5 * time.Minute + options.PowerPollingTimeout = DefaultPowerPollingTimeout } bmc.options = options From 40802a1a616e572385f4594e55909a269386b3c2 Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Thu, 14 Nov 2024 11:25:42 +0100 Subject: [PATCH 6/7] removes ctx error check --- bmc/redfish.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bmc/redfish.go b/bmc/redfish.go index 3fbc682..bc7a0da 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -395,9 +395,6 @@ func (r *RedfishBMC) GetStorages(ctx context.Context, systemUUID string) ([]Stor r.options.ResourcePollingTimeout, true, func(ctx context.Context) (bool, error) { - if ctx.Err() != nil { - return false, ctx.Err() - } systemStorage, err = system.Storage() if err != nil { return false, nil @@ -481,9 +478,6 @@ func (r *RedfishBMC) getSystemByUUID(ctx context.Context, systemUUID string) (*r r.options.ResourcePollingTimeout, true, func(ctx context.Context) (bool, error) { - if ctx.Err() != nil { - return false, ctx.Err() - } var err error systems, err = service.Systems() return err == nil, nil From ce22db29f6f12d2e8bda01b46657be1ee86772c9 Mon Sep 17 00:00:00 2001 From: Stefan Hipfel Date: Fri, 15 Nov 2024 17:39:35 +0100 Subject: [PATCH 7/7] switches back to generic bmc client options struct --- bmc/redfish.go | 24 +++++++++-------- bmc/redfish_kube.go | 6 ++--- bmc/redfish_local.go | 6 ++--- cmd/manager/main.go | 3 ++- internal/controller/bmc_controller.go | 2 +- internal/controller/bmcutils.go | 31 +++++++++++----------- internal/controller/endpoint_controller.go | 31 +++++++++++----------- internal/controller/server_controller.go | 16 +++++------ internal/controller/suite_test.go | 3 ++- 9 files changed, 62 insertions(+), 60 deletions(-) diff --git a/bmc/redfish.go b/bmc/redfish.go index bc7a0da..1ea204b 100644 --- a/bmc/redfish.go +++ b/bmc/redfish.go @@ -29,8 +29,13 @@ const ( DefaultPowerPollingTimeout = 5 * time.Minute ) -// PollingOptions contains the options for polling server resources -type PollingOptions struct { +// 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 @@ -40,7 +45,7 @@ type PollingOptions struct { // RedfishBMC is an implementation of the BMC interface for Redfish. type RedfishBMC struct { client *gofish.APIClient - options PollingOptions + options BMCOptions } var pxeBootWithSettingUEFIBootMode = redfish.Boot{ @@ -56,17 +61,14 @@ 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 PollingOptions, + 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 { diff --git a/bmc/redfish_kube.go b/bmc/redfish_kube.go index 9613867..2bda6b0 100644 --- a/bmc/redfish_kube.go +++ b/bmc/redfish_kube.go @@ -37,13 +37,11 @@ type RedfishKubeBMC struct { // NewRedfishKubeBMCClient creates a new RedfishKubeBMC with the given connection details. func NewRedfishKubeBMCClient( ctx context.Context, - endpoint, username, password string, - basicAuth bool, - options PollingOptions, + options BMCOptions, c client.Client, ns string, ) (BMC, error) { - bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth, options) + bmc, err := NewRedfishBMCClient(ctx, options) if err != nil { return nil, err } diff --git a/bmc/redfish_local.go b/bmc/redfish_local.go index d3f026a..e1561b2 100644 --- a/bmc/redfish_local.go +++ b/bmc/redfish_local.go @@ -20,11 +20,9 @@ type RedfishLocalBMC struct { // NewRedfishLocalBMCClient creates a new RedfishLocalBMC with the given connection details. func NewRedfishLocalBMCClient( ctx context.Context, - endpoint, username, password string, - basicAuth bool, - options PollingOptions, + options BMCOptions, ) (BMC, error) { - bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth, options) + bmc, err := NewRedfishBMCClient(ctx, options) if err != nil { return nil, err } diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 49f6424..dcff0a6 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -226,7 +226,8 @@ func main() { ResyncInterval: serverResyncInterval, EnforceFirstBoot: enforceFirstBoot, EnforcePowerOff: enforcePowerOff, - BMCPollingOptions: bmc.PollingOptions{ + BMCOptions: bmc.BMCOptions{ + BasicAuth: true, PowerPollingInterval: powerPollingInterval, PowerPollingTimeout: powerPollingTimeout, ResourcePollingInterval: resourcePollingInterval, diff --git a/internal/controller/bmc_controller.go b/internal/controller/bmc_controller.go index 6e6506a..273fbda 100644 --- a/internal/controller/bmc_controller.go +++ b/internal/controller/bmc_controller.go @@ -29,7 +29,7 @@ type BMCReconciler struct { client.Client Scheme *runtime.Scheme Insecure bool - BMCPollingOptions bmc.PollingOptions + BMCPollingOptions bmc.BMCOptions } //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=endpoints,verbs=get;list;watch diff --git a/internal/controller/bmcutils.go b/internal/controller/bmcutils.go index 1c44651..aef8c9d 100644 --- a/internal/controller/bmcutils.go +++ b/internal/controller/bmcutils.go @@ -15,7 +15,7 @@ import ( const DefaultKubeNamespace = "default" -func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool, polling bmc.PollingOptions) (bmc.BMC, error) { +func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1alpha1.Server, insecure bool, options bmc.BMCOptions) (bmc.BMC, error) { if server.Spec.BMCRef != nil { b := &metalv1alpha1.BMC{} bmcName := server.Spec.BMCRef.Name @@ -23,7 +23,7 @@ func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1 return nil, fmt.Errorf("failed to get BMC: %w", err) } - return GetBMCClientFromBMC(ctx, c, b, insecure, polling) + return GetBMCClientFromBMC(ctx, c, b, insecure, options) } if server.Spec.BMC != nil { @@ -40,14 +40,14 @@ func GetBMCClientForServer(ctx context.Context, c client.Client, server *metalv1 server.Spec.BMC.Address, server.Spec.BMC.Protocol.Port, bmcSecret, - polling, + options, ) } return nil, fmt.Errorf("server %s has neither a BMCRef nor a BMC configured", server.Name) } -func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC, insecure bool, polling bmc.PollingOptions) (bmc.BMC, error) { +func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1alpha1.BMC, insecure bool, options bmc.BMCOptions) (bmc.BMC, error) { var address string if bmcObj.Spec.EndpointRef != nil { @@ -67,7 +67,7 @@ func GetBMCClientFromBMC(ctx context.Context, c client.Client, bmcObj *metalv1al return nil, fmt.Errorf("failed to get BMC secret: %w", err) } - return CreateBMCClient(ctx, c, insecure, bmcObj.Spec.Protocol.Name, address, bmcObj.Spec.Protocol.Port, bmcSecret, polling) + return CreateBMCClient(ctx, c, insecure, bmcObj.Spec.Protocol.Name, address, bmcObj.Spec.Protocol.Port, bmcSecret, options) } func CreateBMCClient( @@ -78,7 +78,7 @@ func CreateBMCClient( address string, port int32, bmcSecret *metalv1alpha1.BMCSecret, - polling bmc.PollingOptions, + bmcOptions bmc.BMCOptions, ) (bmc.BMC, error) { protocol := "https" if insecure { @@ -86,34 +86,35 @@ func CreateBMCClient( } var bmcClient bmc.BMC + var err error switch bmcProtocol { case metalv1alpha1.ProtocolRedfish: - endpoint := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - username, password, err := GetBMCCredentialsFromSecret(bmcSecret) + bmcOptions.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + bmcOptions.Username, bmcOptions.Password, err = GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishBMCClient(ctx, endpoint, username, password, true, polling) + bmcClient, err = bmc.NewRedfishBMCClient(ctx, bmcOptions) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } case metalv1alpha1.ProtocolRedfishLocal: - endpoint := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - username, password, err := GetBMCCredentialsFromSecret(bmcSecret) + bmcOptions.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + bmcOptions.Username, bmcOptions.Password, err = GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishLocalBMCClient(ctx, endpoint, username, password, true, polling) + bmcClient, err = bmc.NewRedfishLocalBMCClient(ctx, bmcOptions) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } case metalv1alpha1.ProtocolRedfishKube: - endpoint := fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) - username, password, err := GetBMCCredentialsFromSecret(bmcSecret) + bmcOptions.Endpoint = fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(address, fmt.Sprintf("%d", port))) + bmcOptions.Username, bmcOptions.Password, err = GetBMCCredentialsFromSecret(bmcSecret) if err != nil { return nil, fmt.Errorf("failed to get credentials from BMC secret: %w", err) } - bmcClient, err = bmc.NewRedfishKubeBMCClient(ctx, endpoint, username, password, true, polling, c, DefaultKubeNamespace) + bmcClient, err = bmc.NewRedfishKubeBMCClient(ctx, bmcOptions, c, DefaultKubeNamespace) if err != nil { return nil, fmt.Errorf("failed to create Redfish client: %w", err) } diff --git a/internal/controller/endpoint_controller.go b/internal/controller/endpoint_controller.go index 0590bbb..e8b32a8 100644 --- a/internal/controller/endpoint_controller.go +++ b/internal/controller/endpoint_controller.go @@ -29,10 +29,10 @@ const ( // EndpointReconciler reconciles a Endpoints object type EndpointReconciler struct { client.Client - Scheme *runtime.Scheme - MACPrefixes *macdb.MacPrefixes - Insecure bool - BMCPollingOptions bmc.PollingOptions + Scheme *runtime.Scheme + MACPrefixes *macdb.MacPrefixes + Insecure bool + BMCOptions bmc.BMCOptions } //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=bmcs,verbs=get;list;watch;create;update;patch;delete @@ -82,12 +82,17 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end if len(m.DefaultCredentials) == 0 { return ctrl.Result{}, fmt.Errorf("no default credentials present for BMC %s", endpoint.Spec.MACAddress) } + bmcOptions := bmc.BMCOptions{ + BasicAuth: true, + Username: m.DefaultCredentials[0].Username, + Password: m.DefaultCredentials[0].Password, + } switch m.Protocol { case metalv1alpha1.ProtocolRedfish: log.V(1).Info("Creating client for BMC") - bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - log.V(1).Info("Creating client for BMC", "Address", bmcAddress) - bmcClient, err := bmc.NewRedfishBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true, r.BMCPollingOptions) + bmcOptions.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + log.V(1).Info("Creating client for BMC", "Address", bmcOptions.Endpoint) + bmcClient, err := bmc.NewRedfishBMCClient(ctx, bmcOptions) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } @@ -107,8 +112,8 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end log.V(1).Info("Applied BMC object for endpoint") case metalv1alpha1.ProtocolRedfishLocal: log.V(1).Info("Creating client for a local test BMC") - bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) - bmcClient, err := bmc.NewRedfishLocalBMCClient(ctx, bmcAddress, m.DefaultCredentials[0].Username, m.DefaultCredentials[0].Password, true, r.BMCPollingOptions) + bmcOptions.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + bmcClient, err := bmc.NewRedfishLocalBMCClient(ctx, bmcOptions) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) } @@ -126,14 +131,10 @@ func (r *EndpointReconciler) reconcile(ctx context.Context, log logr.Logger, end log.V(1).Info("Applied BMC object for Endpoint") case metalv1alpha1.ProtocolRedfishKube: log.V(1).Info("Creating client for a kube test BMC") - bmcAddress := fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) + bmcOptions.Endpoint = fmt.Sprintf("%s://%s", r.getProtocol(), net.JoinHostPort(endpoint.Spec.IP.String(), fmt.Sprintf("%d", m.Port))) bmcClient, err := bmc.NewRedfishKubeBMCClient( ctx, - bmcAddress, - m.DefaultCredentials[0].Username, - m.DefaultCredentials[0].Password, - true, - r.BMCPollingOptions, + bmcOptions, r.Client, DefaultKubeNamespace) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to create BMC client: %w", err) diff --git a/internal/controller/server_controller.go b/internal/controller/server_controller.go index 7c8d7ed..4de9610 100644 --- a/internal/controller/server_controller.go +++ b/internal/controller/server_controller.go @@ -62,7 +62,7 @@ type ServerReconciler struct { EnforceFirstBoot bool EnforcePowerOff bool ResyncInterval time.Duration - BMCPollingOptions bmc.PollingOptions + BMCOptions bmc.BMCOptions DiscoveryTimeout time.Duration } @@ -272,7 +272,7 @@ func (r *ServerReconciler) handleDiscoveryState(ctx context.Context, log logr.Lo } log.V(1).Info("Server state set to power on") - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } @@ -416,7 +416,7 @@ func (r *ServerReconciler) updateServerStatus(ctx context.Context, log logr.Logg log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -614,7 +614,7 @@ func (r *ServerReconciler) pxeBootServer(ctx context.Context, log logr.Logger, s return fmt.Errorf("can only PXE boot server with valid BMC ref or inline BMC configuration") } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) defer func() { if bmcClient != nil { bmcClient.Logout() @@ -705,7 +705,7 @@ func (r *ServerReconciler) ensureServerPowerState(ctx context.Context, log logr. return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) defer func() { if bmcClient != nil { bmcClient.Logout() @@ -815,7 +815,7 @@ func (r *ServerReconciler) applyBootOrder(ctx context.Context, log logr.Logger, log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -849,7 +849,7 @@ func (r *ServerReconciler) applyBiosSettings(ctx context.Context, log logr.Logge log.V(1).Info("Server has no BMC connection configured") return nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) if err != nil { return fmt.Errorf("failed to create BMC client: %w", err) } @@ -901,7 +901,7 @@ func (r *ServerReconciler) handleAnnotionOperations(ctx context.Context, log log if !ok { return false, nil } - bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCPollingOptions) + bmcClient, err := GetBMCClientForServer(ctx, r.Client, server, r.Insecure, r.BMCOptions) if err != nil { return false, fmt.Errorf("failed to create BMC client: %w", err) } diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 4cbb314..dde67ba 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -182,9 +182,10 @@ func SetupTest() *corev1.Namespace { RegistryResyncInterval: 50 * time.Millisecond, ResyncInterval: 100 * time.Millisecond, EnforceFirstBoot: true, - BMCPollingOptions: bmc.PollingOptions{ + BMCOptions: bmc.BMCOptions{ PowerPollingInterval: 50 * time.Millisecond, PowerPollingTimeout: 200 * time.Millisecond, + BasicAuth: true, }, DiscoveryTimeout: 500 * time.Millisecond, // Force timeout to be quick for tests }).SetupWithManager(k8sManager)).To(Succeed())