Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f8a4f93
Regenerate apply configurations with controller-gen v0.21.0
fwiesel Jul 9, 2026
4dc3f92
Migrate EvictionReconciler to SSA status updates
fwiesel May 5, 2026
ceb5f22
Migrate HypervisorController to SSA status updates
fwiesel May 5, 2026
394a1a9
Migrate HypervisorTaintController to SSA status updates
fwiesel May 5, 2026
3663526
Migrate HypervisorMaintenanceController to SSA status updates
fwiesel May 5, 2026
5e18d59
Migrate OnboardingController to SSA status updates
fwiesel May 5, 2026
9829117
Migrate HypervisorOffboardingReconciler to SSA status updates
fwiesel May 5, 2026
a8e8ce8
Migrate AggregatesController to SSA status updates
fwiesel May 5, 2026
dcfd204
Migrate TraitsController to SSA status updates
fwiesel May 5, 2026
67dfedf
Migrate ready.Controller to SSA status updates; remove PatchHyperviso…
fwiesel May 5, 2026
5525996
Migrate HypervisorMaintenanceController Eviction creation to SSA
fwiesel May 6, 2026
c538ab7
Migrate NodeCertificateController Certificate creation to SSA
fwiesel May 6, 2026
27e1ee5
Refactor HypervisorMaintenanceController: pass statusCfg into sub-fun…
fwiesel May 6, 2026
b4f6ff9
Refactor OnboardingController: statusCfg passed through reconcile chain
fwiesel May 6, 2026
5b95d8a
Fix conditions helpers and HypervisorController status apply issues
fwiesel Jul 8, 2026
03abfc9
Fix SSA condition seeding: scope each controller to its own conditions
fwiesel Jul 8, 2026
82d5221
Adapt SSA call sites to controller-gen v0.21.0 signatures
fwiesel Jul 9, 2026
9d785cb
Fix label propagation and forced_down clearing on service enable
fwiesel Jul 9, 2026
faeb11e
HypervisorMaintenanceController: delegate SetupWithManager to registe…
fwiesel Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 74 additions & 16 deletions internal/controller/aggregates_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,24 @@ package controller

import (
"context"
"encoding/json"
"errors"
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
k8sacmetav1 "k8s.io/client-go/applyconfigurations/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/gophercloud/gophercloud/v2"

kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
apiv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/applyconfigurations/api/v1"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/openstack"
"github.com/cobaltcore-dev/openstack-hypervisor-operator/internal/utils"
)
Expand Down Expand Up @@ -76,17 +80,32 @@ func (ac *AggregatesController) Reconcile(ctx context.Context, req ctrl.Request)
// Apply aggregates to OpenStack and update status
aggregates, err := openstack.ApplyAggregates(ctx, ac.computeClient, hv.Name, desiredAggregateNames)
if err != nil {
// Set error condition with retry on conflict
condition := metav1.Condition{
Type: kvmv1.ConditionTypeAggregatesUpdated,
Status: metav1.ConditionFalse,
Reason: kvmv1.ConditionReasonFailed,
Message: fmt.Errorf("failed to apply aggregates: %w", err).Error(),
// Set error condition, preserving current aggregate ownership
statusCfg := apiv1.HypervisorStatus()
if c := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeAggregatesUpdated); c != nil {
statusCfg.WithConditions(utils.ConditionFromStatus(*c))
}
utils.SetApplyConfigurationStatusCondition(&statusCfg.Conditions,
*k8sacmetav1.Condition().
WithType(kvmv1.ConditionTypeAggregatesUpdated).
WithStatus(metav1.ConditionFalse).
WithReason(kvmv1.ConditionReasonFailed).
WithMessage(fmt.Errorf("failed to apply aggregates: %w", err).Error()))
if len(hv.Status.Aggregates) > 0 {
aggCfgs := make([]apiv1.AggregateApplyConfiguration, len(hv.Status.Aggregates))
for i, agg := range hv.Status.Aggregates {
a := apiv1.Aggregate().WithName(agg.Name).WithUUID(agg.UUID)
if len(agg.Metadata) > 0 {
a.WithMetadata(agg.Metadata)
}
aggCfgs[i] = *a
}
statusCfg.Aggregates = aggCfgs
}

if err2 := utils.PatchHypervisorStatusWithRetry(ctx, ac.Client, hv.Name, AggregatesControllerName, func(h *kvmv1.Hypervisor) {
meta.SetStatusCondition(&h.Status.Conditions, condition)
}); err2 != nil {
if err2 := ac.Status().Apply(ctx,
apiv1.Hypervisor(hv.Name).WithStatus(statusCfg),
k8sclient.ForceOwnership, k8sclient.FieldOwner(AggregatesControllerName)); err2 != nil {
return ctrl.Result{}, errors.Join(err, err2)
}
return ctrl.Result{}, err
Expand All @@ -106,15 +125,54 @@ func (ac *AggregatesController) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, nil
}

// Patch status with retry on conflict
err := utils.PatchHypervisorStatusWithRetry(ctx, ac.Client, hv.Name, AggregatesControllerName, func(h *kvmv1.Hypervisor) {
if aggregatesChanged {
h.Status.Aggregates = newAggregates
statusCfg := apiv1.HypervisorStatus()
if c := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeAggregatesUpdated); c != nil {
statusCfg.WithConditions(utils.ConditionFromStatus(*c))
}
utils.SetApplyConfigurationStatusCondition(&statusCfg.Conditions,
*k8sacmetav1.Condition().
WithType(desiredCondition.Type).
WithStatus(desiredCondition.Status).
WithReason(desiredCondition.Reason).
WithMessage(desiredCondition.Message))

if aggregatesChanged && len(newAggregates) > 0 {
aggCfgs := make([]apiv1.AggregateApplyConfiguration, len(newAggregates))
for i, agg := range newAggregates {
a := apiv1.Aggregate().WithName(agg.Name).WithUUID(agg.UUID)
if len(agg.Metadata) > 0 {
a.WithMetadata(agg.Metadata)
}
aggCfgs[i] = *a
}
statusCfg.Aggregates = aggCfgs
}

if err := ac.Status().Apply(ctx,
apiv1.Hypervisor(hv.Name).WithStatus(statusCfg),
k8sclient.ForceOwnership, k8sclient.FieldOwner(AggregatesControllerName)); err != nil {
return ctrl.Result{}, err
}

// The Aggregates field uses omitempty in the generated apply config, so an
// empty slice cannot be sent via SSA. Use a targeted merge patch to clear it.
if aggregatesChanged && len(newAggregates) == 0 {
patch, err := json.Marshal(map[string]any{
"status": map[string]any{
"aggregates": []kvmv1.Aggregate{},
},
})
if err != nil {
return ctrl.Result{}, err
}
fresh := &kvmv1.Hypervisor{}
if err := ac.Get(ctx, k8sclient.ObjectKey{Name: hv.Name}, fresh); err != nil {
return ctrl.Result{}, err
}
meta.SetStatusCondition(&h.Status.Conditions, desiredCondition)
})
return ctrl.Result{}, ac.Status().Patch(ctx, fresh, k8sclient.RawPatch(types.MergePatchType, patch))
}

return ctrl.Result{}, err
return ctrl.Result{}, nil
}

// determineDesiredState returns the desired aggregates and the corresponding condition
Expand Down
Loading
Loading