@@ -24,7 +24,6 @@ import (
2424 "time"
2525
2626 "github.com/go-logr/logr"
27- "github.com/gophercloud/gophercloud/v2/openstack/baremetal/v1/nodes"
2827 "k8s.io/apimachinery/pkg/api/equality"
2928 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3029 "k8s.io/apimachinery/pkg/runtime"
@@ -37,30 +36,24 @@ import (
3736 "sigs.k8s.io/controller-runtime/pkg/predicate"
3837
3938 v1alpha1 "github.com/osac-project/bare-metal-fulfillment-operator/api/v1alpha1"
40- "github.com/osac-project/host-management-openstack/internal/ironic "
39+ "github.com/osac-project/host-management-openstack/internal/management "
4140 opv1alpha1 "github.com/osac-project/osac-operator/api/v1alpha1"
4241 "github.com/osac-project/osac-operator/pkg/provisioning"
4342)
4443
45- // HostLeaseReconciler reconciles HostLease CRs for power management via Ironic.
4644type HostLeaseReconciler struct {
4745 client.Client
48- Scheme * runtime.Scheme
49- IronicClient ironic.NodeClient
50- ProvisioningProvider provisioning.ProvisioningProvider
51-
52- // RecheckInterval is the interval for polling Ironic until power state matches desired state.
53- RecheckInterval time.Duration
54-
55- // ProvisionPollInterval is the interval for polling AAP job status.
46+ Scheme * runtime.Scheme
47+ ManagementClient management.Client
48+ ProvisioningProvider provisioning.ProvisioningProvider
49+ RecheckInterval time.Duration
5650 ProvisionPollInterval time.Duration
5751}
5852
59- // NewHostLeaseReconciler creates a new HostLeaseReconciler with defaults applied.
6053func NewHostLeaseReconciler (
6154 client client.Client ,
6255 scheme * runtime.Scheme ,
63- ironicClient ironic. NodeClient ,
56+ managementClient management. Client ,
6457 provider provisioning.ProvisioningProvider ,
6558 recheckInterval time.Duration ,
6659) * HostLeaseReconciler {
@@ -71,7 +64,7 @@ func NewHostLeaseReconciler(
7164 return & HostLeaseReconciler {
7265 Client : client ,
7366 Scheme : scheme ,
74- IronicClient : ironicClient ,
67+ ManagementClient : managementClient ,
7568 ProvisioningProvider : provider ,
7669 RecheckInterval : recheckInterval ,
7770 ProvisionPollInterval : DefaultProvisionPollInterval ,
@@ -143,34 +136,44 @@ func (r *HostLeaseReconciler) handleUpdate(ctx context.Context, hostLease *v1alp
143136 }
144137 }
145138
146- node , err := r .IronicClient . GetNode (ctx , hostLease .Spec .ExternalHostID )
139+ powerStatus , err := r .ManagementClient . GetPowerState (ctx , hostLease .Spec .ExternalHostID )
147140 if err != nil {
148- log .Error (err , "failed to get Ironic node " , "nodeID" , hostLease .Spec .ExternalHostID )
141+ log .Error (err , "failed to get power state " , "nodeID" , hostLease .Spec .ExternalHostID )
149142 r .syncHostLeaseStatus (hostLease , nil , err , log )
150143 return ctrl.Result {}, err
151144 }
152- log .V (1 ).Info ("Ironic node" , "nodeID" , hostLease .Spec .ExternalHostID , "power_state" , node .PowerState )
145+ if powerStatus == nil {
146+ err := fmt .Errorf ("management backend returned nil power status for host %s" , hostLease .Spec .ExternalHostID )
147+ log .Error (err , "unexpected nil power status" , "nodeID" , hostLease .Spec .ExternalHostID )
148+ r .syncHostLeaseStatus (hostLease , nil , err , log )
149+ return ctrl.Result {}, err
150+ }
151+ log .V (1 ).Info ("Host power state" , "nodeID" , hostLease .Spec .ExternalHostID , "power_state" , powerStatus .State )
153152
154153 if hostLease .Spec .PoweredOn != nil {
155- if err := r .reconcilePower (ctx , hostLease , node , log ); err != nil {
154+ if err := r .reconcilePower (ctx , hostLease , powerStatus , log ); err != nil {
156155 r .syncHostLeaseStatus (hostLease , nil , err , log )
157156 return ctrl.Result {}, err
158157 }
159158
160- node , err = r .IronicClient . GetNode (ctx , hostLease .Spec .ExternalHostID )
159+ powerStatus , err = r .ManagementClient . GetPowerState (ctx , hostLease .Spec .ExternalHostID )
161160 if err != nil {
162- log .Error (err , "failed to refresh node after power reconciliation" , "nodeID" , hostLease .Spec .ExternalHostID )
161+ log .Error (err , "failed to refresh power state after reconciliation" , "nodeID" , hostLease .Spec .ExternalHostID )
162+ r .syncHostLeaseStatus (hostLease , nil , err , log )
163+ return ctrl.Result {}, err
164+ }
165+ if powerStatus == nil {
166+ err := fmt .Errorf ("management backend returned nil power status for host %s" , hostLease .Spec .ExternalHostID )
167+ log .Error (err , "unexpected nil power status after reconciliation" , "nodeID" , hostLease .Spec .ExternalHostID )
163168 r .syncHostLeaseStatus (hostLease , nil , err , log )
164169 return ctrl.Result {}, err
165170 }
166171 }
167172
168- r .syncHostLeaseStatus (hostLease , node , nil , log )
173+ r .syncHostLeaseStatus (hostLease , powerStatus , nil , log )
169174
170175 if hostLease .Spec .PoweredOn != nil {
171- currentlyOn := node .PowerState == ironic .PowerOn .String ()
172- if * hostLease .Spec .PoweredOn != currentlyOn {
173- hostLease .Status .Phase = v1alpha1 .HostLeasePhaseProgressing
176+ if powerStatus .IsTransitioning || * hostLease .Spec .PoweredOn != (powerStatus .State == management .PowerOn ) {
174177 return ctrl.Result {RequeueAfter : r .RecheckInterval }, nil
175178 }
176179 }
@@ -303,38 +306,41 @@ func (r *HostLeaseReconciler) validateOpenStackHost(hostLease *v1alpha1.HostLeas
303306 return true
304307}
305308
306- func (r * HostLeaseReconciler ) reconcilePower (ctx context.Context , hostLease * v1alpha1.HostLease , node * nodes. Node , log logr.Logger ) error {
307- currentlyOn := node . PowerState == ironic .PowerOn . String ()
309+ func (r * HostLeaseReconciler ) reconcilePower (ctx context.Context , hostLease * v1alpha1.HostLease , powerStatus * management. PowerStatus , log logr.Logger ) error {
310+ currentlyOn := powerStatus . State == management .PowerOn
308311 desiredOn := * hostLease .Spec .PoweredOn
309312
310- // If Ironic is already processing a power state change, skip to avoid 409 Conflict.
311- if r .IronicClient .IsNodePowerTransitioning (node ) {
313+ if powerStatus .IsTransitioning {
312314 log .V (1 ).Info ("Node is transitioning, skipping power action" ,
313- "nodeID" , hostLease .Spec .ExternalHostID ,
314- "targetPowerState" , node .TargetPowerState )
315+ "nodeID" , hostLease .Spec .ExternalHostID )
315316 return nil
316317 }
317318
318- var err error
319319 needsPowerUpdate := desiredOn != currentlyOn
320320 if ! needsPowerUpdate {
321- log .Info ("Power state already matches desired" , "poweredOn" , desiredOn , "power_state" , node . PowerState )
321+ log .Info ("Power state already matches desired" , "poweredOn" , desiredOn , "power_state" , powerStatus . State )
322322 return nil
323323 }
324324
325- targetState := ironic .PowerOff
325+ targetState := management .PowerOff
326326 action := "off"
327327 if desiredOn {
328- targetState = ironic .PowerOn
328+ targetState = management .PowerOn
329329 action = "on"
330330 }
331331
332332 log .Info ("Powering " + action + " node" , "nodeID" , hostLease .Spec .ExternalHostID )
333- if err = r .IronicClient .SetPowerState (ctx , hostLease .Spec .ExternalHostID , targetState ); err != nil {
333+ if err := r .ManagementClient .SetPowerState (ctx , hostLease .Spec .ExternalHostID , targetState ); err != nil {
334+ if errors .Is (err , management .ErrTransitioning ) {
335+ log .Info ("Node is transitioning (conflict), will retry" ,
336+ "nodeID" , hostLease .Spec .ExternalHostID )
337+ return nil
338+ }
334339 log .Error (err , "failed to power " + action + " node" , "nodeID" , hostLease .Spec .ExternalHostID )
340+ return err
335341 }
336342
337- return err
343+ return nil
338344}
339345
340346func (r * HostLeaseReconciler ) reconcileProvisioning (ctx context.Context , hostLease * v1alpha1.HostLease ) (ctrl.Result , error ) {
@@ -393,8 +399,7 @@ func (r *HostLeaseReconciler) reconcileProvisioning(ctx context.Context, hostLea
393399 return result , nil
394400}
395401
396- // syncHostLeaseStatus syncs power-related conditions and observed power state in memory.
397- func (r * HostLeaseReconciler ) syncHostLeaseStatus (hostLease * v1alpha1.HostLease , node * nodes.Node , reconcileErr error , log logr.Logger ) {
402+ func (r * HostLeaseReconciler ) syncHostLeaseStatus (hostLease * v1alpha1.HostLease , powerStatus * management.PowerStatus , reconcileErr error , log logr.Logger ) {
398403 if reconcileErr != nil {
399404 hostLease .Status .Phase = v1alpha1 .HostLeasePhaseFailed
400405 hostLease .SetStatusCondition (
@@ -407,13 +412,17 @@ func (r *HostLeaseReconciler) syncHostLeaseStatus(hostLease *v1alpha1.HostLease,
407412 return
408413 }
409414
410- if node == nil {
415+ if powerStatus == nil {
411416 return
412417 }
413418
414- poweredOn := node . PowerState == ironic .PowerOn . String ()
419+ poweredOn := powerStatus . State == management .PowerOn
415420 hostLease .Status .PoweredOn = & poweredOn
416421
422+ if powerStatus .IsTransitioning {
423+ return
424+ }
425+
417426 if hostLease .Spec .PoweredOn != nil && * hostLease .Spec .PoweredOn != poweredOn {
418427 hostLease .SetStatusCondition (
419428 v1alpha1 .HostConditionPowerSynced ,
0 commit comments