Skip to content

Commit 800ed63

Browse files
committed
Only Aggregate contoller changes aggregates and store uuids
For Cortex, we want the uuids in addition to the names, so keep track of them in the status. Let's keep only the aggregates controller changing aggregates.
1 parent b2957f8 commit 800ed63

File tree

14 files changed

+687
-519
lines changed

14 files changed

+687
-519
lines changed

api/v1/hypervisor_types.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,17 @@ const (
6161
ConditionReasonReadyEvicting = "Evicting"
6262

6363
// ConditionTypeOnboarding reasons
64-
ConditionReasonInitial = "Initial"
65-
ConditionReasonOnboarding = "Onboarding"
66-
ConditionReasonTesting = "Testing"
67-
ConditionReasonAborted = "Aborted"
64+
ConditionReasonInitial = "Initial"
65+
ConditionReasonOnboarding = "Onboarding"
66+
ConditionReasonTesting = "Testing"
67+
ConditionReasonRemovingTestAggregate = "RemovingTestAggregate"
68+
ConditionReasonAborted = "Aborted"
69+
70+
// ConditionTypeAggregatesUpdated reasons
71+
// Note: ConditionReasonSucceeded and ConditionReasonFailed are shared with eviction_types.go
72+
ConditionReasonTestAggregates = "TestAggregates"
73+
ConditionReasonTerminating = "Terminating"
74+
ConditionReasonEvictionInProgress = "EvictionInProgress"
6875
)
6976

7077
// HypervisorSpec defines the desired state of Hypervisor
@@ -347,6 +354,10 @@ type HypervisorStatus struct {
347354
// Aggregates are the applied aggregates of the hypervisor.
348355
Aggregates []string `json:"aggregates,omitempty"`
349356

357+
// +kubebuilder:default:={}
358+
// The UUIDs of the aggregates are used to apply aggregates to the hypervisor.
359+
AggregateUUIDs []string `json:"aggregateUUIDs,omitempty"`
360+
350361
// InternalIP is the internal IP address of the hypervisor.
351362
InternalIP string `json:"internalIp,omitempty"`
352363

api/v1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

applyconfigurations/api/v1/hypervisorstatus.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/openstack-hypervisor-operator/crds/hypervisor-crd.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ spec:
200200
status:
201201
description: HypervisorStatus defines the observed state of Hypervisor
202202
properties:
203+
aggregateUUIDs:
204+
default: []
205+
description: The UUIDs of the aggregates are used to apply aggregates
206+
to the hypervisor.
207+
items:
208+
type: string
209+
type: array
203210
aggregates:
204211
description: Aggregates are the applied aggregates of the hypervisor.
205212
items:

config/crd/bases/kvm.cloud.sap_hypervisors.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,13 @@ spec:
201201
status:
202202
description: HypervisorStatus defines the observed state of Hypervisor
203203
properties:
204+
aggregateUUIDs:
205+
default: []
206+
description: The UUIDs of the aggregates are used to apply aggregates
207+
to the hypervisor.
208+
items:
209+
type: string
210+
type: array
204211
aggregates:
205212
description: Aggregates are the applied aggregates of the hypervisor.
206213
items:

internal/controller/aggregates_controller.go

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"fmt"
2424
"slices"
2525

26+
corev1 "k8s.io/api/core/v1"
27+
"k8s.io/apimachinery/pkg/api/equality"
2628
"k8s.io/apimachinery/pkg/api/meta"
2729
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
"k8s.io/apimachinery/pkg/runtime"
@@ -57,56 +59,111 @@ func (ac *AggregatesController) Reconcile(ctx context.Context, req ctrl.Request)
5759
return ctrl.Result{}, k8sclient.IgnoreNotFound(err)
5860
}
5961

60-
/// On- and off-boarding need to mess with the aggregates, so let's get out of their way
61-
if !meta.IsStatusConditionFalse(hv.Status.Conditions, kvmv1.ConditionTypeOnboarding) ||
62-
meta.IsStatusConditionTrue(hv.Status.Conditions, kvmv1.ConditionTypeTerminating) {
62+
// Wait for onboarding controller to populate HypervisorID and ServiceID
63+
// before attempting to modify aggregates
64+
if hv.Status.HypervisorID == "" || hv.Status.ServiceID == "" {
6365
return ctrl.Result{}, nil
6466
}
6567

66-
if slices.Equal(hv.Spec.Aggregates, hv.Status.Aggregates) {
67-
// Nothing to be done
68-
return ctrl.Result{}, nil
68+
base := hv.DeepCopy()
69+
desiredAggregates, desiredCondition := ac.determineDesiredState(hv)
70+
71+
if !slices.Equal(desiredAggregates, hv.Status.Aggregates) {
72+
// Apply aggregates to OpenStack and update status
73+
uuids, err := openstack.ApplyAggregates(ctx, ac.computeClient, hv.Name, desiredAggregates)
74+
if err != nil {
75+
// Set error condition
76+
condition := metav1.Condition{
77+
Type: kvmv1.ConditionTypeAggregatesUpdated,
78+
Status: metav1.ConditionFalse,
79+
Reason: kvmv1.ConditionReasonFailed,
80+
Message: fmt.Errorf("failed to apply aggregates: %w", err).Error(),
81+
}
82+
83+
if meta.SetStatusCondition(&hv.Status.Conditions, condition) {
84+
if err2 := ac.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(base,
85+
k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(AggregatesControllerName)); err2 != nil {
86+
return ctrl.Result{}, errors.Join(err, err2)
87+
}
88+
}
89+
return ctrl.Result{}, err
90+
}
91+
92+
hv.Status.Aggregates = desiredAggregates
93+
hv.Status.AggregateUUIDs = uuids
6994
}
7095

71-
err := openstack.ApplyAggregates(ctx, ac.computeClient, hv.Name, hv.Spec.Aggregates)
72-
if err != nil {
73-
err = fmt.Errorf("failed to apply aggregates: %w", err)
74-
if err2 := ac.setErrorCondition(ctx, hv, err.Error()); err2 != nil {
75-
return ctrl.Result{}, errors.Join(err, err2)
76-
}
77-
return ctrl.Result{}, err
96+
// Set the condition based on the determined desired state
97+
meta.SetStatusCondition(&hv.Status.Conditions, desiredCondition)
98+
99+
if equality.Semantic.DeepEqual(base, hv) {
100+
return ctrl.Result{}, nil
78101
}
79102

80-
base := hv.DeepCopy()
81-
hv.Status.Aggregates = hv.Spec.Aggregates
82-
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
83-
Type: kvmv1.ConditionTypeAggregatesUpdated,
84-
Status: metav1.ConditionTrue,
85-
Reason: kvmv1.ConditionReasonSucceeded,
86-
Message: "Aggregates updated successfully",
87-
})
88103
return ctrl.Result{}, ac.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(base,
89104
k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(AggregatesControllerName))
90105
}
91106

92-
// setErrorCondition sets the error condition on the Hypervisor status, returns error if update fails
93-
func (ac *AggregatesController) setErrorCondition(ctx context.Context, hv *kvmv1.Hypervisor, msg string) error {
94-
condition := metav1.Condition{
95-
Type: kvmv1.ConditionTypeAggregatesUpdated,
96-
Status: metav1.ConditionFalse,
97-
Reason: kvmv1.ConditionReasonFailed,
98-
Message: msg,
107+
// determineDesiredState returns the desired aggregates and the corresponding condition
108+
// based on the hypervisor's current state. The condition status is True only when
109+
// spec aggregates are being applied. Otherwise, it's False with a reason explaining
110+
// why different aggregates are applied.
111+
func (ac *AggregatesController) determineDesiredState(hv *kvmv1.Hypervisor) ([]string, metav1.Condition) {
112+
// If terminating AND evicted, remove from all aggregates
113+
// We must wait for eviction to complete before removing aggregates
114+
if meta.IsStatusConditionTrue(hv.Status.Conditions, kvmv1.ConditionTypeTerminating) {
115+
evictingCondition := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeEvicting)
116+
// Only remove aggregates if eviction is complete (Evicting=False)
117+
// If Evicting condition is not set or still True, keep current aggregates
118+
if evictingCondition != nil && evictingCondition.Status == metav1.ConditionFalse {
119+
return []string{}, metav1.Condition{
120+
Type: kvmv1.ConditionTypeAggregatesUpdated,
121+
Status: metav1.ConditionFalse,
122+
Reason: kvmv1.ConditionReasonTerminating,
123+
Message: "Aggregates cleared due to termination after eviction",
124+
}
125+
}
126+
// Still evicting or eviction not started - keep current aggregates
127+
return hv.Status.Aggregates, metav1.Condition{
128+
Type: kvmv1.ConditionTypeAggregatesUpdated,
129+
Status: metav1.ConditionFalse,
130+
Reason: kvmv1.ConditionReasonEvictionInProgress,
131+
Message: "Aggregates unchanged while terminating and eviction in progress",
132+
}
99133
}
100134

101-
base := hv.DeepCopy()
102-
if meta.SetStatusCondition(&hv.Status.Conditions, condition) {
103-
if err := ac.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(base,
104-
k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(AggregatesControllerName)); err != nil {
105-
return err
135+
// If onboarding is in progress (Initial or Testing), add test aggregate
136+
onboardingCondition := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeOnboarding)
137+
if onboardingCondition != nil && onboardingCondition.Status == metav1.ConditionTrue {
138+
if onboardingCondition.Reason == kvmv1.ConditionReasonInitial ||
139+
onboardingCondition.Reason == kvmv1.ConditionReasonTesting {
140+
zone := hv.Labels[corev1.LabelTopologyZone]
141+
return []string{zone, testAggregateName}, metav1.Condition{
142+
Type: kvmv1.ConditionTypeAggregatesUpdated,
143+
Status: metav1.ConditionFalse,
144+
Reason: kvmv1.ConditionReasonTestAggregates,
145+
Message: "Test aggregate applied during onboarding instead of spec aggregates",
146+
}
147+
}
148+
149+
// If removing test aggregate, use Spec.Aggregates (no test aggregate)
150+
if onboardingCondition.Reason == kvmv1.ConditionReasonRemovingTestAggregate {
151+
return hv.Spec.Aggregates, metav1.Condition{
152+
Type: kvmv1.ConditionTypeAggregatesUpdated,
153+
Status: metav1.ConditionTrue,
154+
Reason: kvmv1.ConditionReasonSucceeded,
155+
Message: "Aggregates from spec applied successfully",
156+
}
106157
}
107158
}
108159

109-
return nil
160+
// Normal operations or onboarding complete: use Spec.Aggregates
161+
return hv.Spec.Aggregates, metav1.Condition{
162+
Type: kvmv1.ConditionTypeAggregatesUpdated,
163+
Status: metav1.ConditionTrue,
164+
Reason: kvmv1.ConditionReasonSucceeded,
165+
Message: "Aggregates from spec applied successfully",
166+
}
110167
}
111168

112169
// SetupWithManager sets up the controller with the Manager.
@@ -118,7 +175,6 @@ func (ac *AggregatesController) SetupWithManager(mgr ctrl.Manager) error {
118175
if ac.computeClient, err = openstack.GetServiceClient(ctx, "compute", nil); err != nil {
119176
return err
120177
}
121-
ac.computeClient.Microversion = "2.40" // gophercloud only supports numeric ids
122178

123179
return ctrl.NewControllerManagedBy(mgr).
124180
Named(AggregatesControllerName).

0 commit comments

Comments
 (0)