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{