Skip to content

Commit 03431a8

Browse files
committed
Move InstanceHa Enabling/Disabling to own controller
The instance ha controller will eventually move out to the instance-ha service itself. But for now, we keep it as a stand in to reflect the spec value over the https api.
1 parent 201d8f5 commit 03431a8

9 files changed

+461
-21
lines changed

api/v1/eviction_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ const (
5252
// ConditionTypeHypervisorDisabled is the type of condition for hypervisor disabled status
5353
ConditionTypeHypervisorDisabled = "HypervisorDisabled"
5454

55+
// ConditionTypeHaEnabled is the type of condition for signalling if HA is enabled / disabled for the hypervisor
56+
ConditionTypeHaEnabled = "HaEnabled"
57+
5558
// ConditionTypeEvicting is the type of condition for eviction status
5659
ConditionTypeEvicting = "Evicting"
5760
)

api/v1/hypervisor_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ const (
7272
ConditionReasonTestAggregates = "TestAggregates"
7373
ConditionReasonTerminating = "Terminating"
7474
ConditionReasonEvictionInProgress = "EvictionInProgress"
75+
76+
// ConditionTypeHaEnabled reasons
77+
ConditionReasonHaEvicted = "Evicted" // HA disabled due to eviction
7578
)
7679

7780
// HypervisorSpec defines the desired state of Hypervisor

cmd/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,13 @@ func main() {
252252
setupLog.Error(err, "unable to create controller", "controller", "Eviction")
253253
os.Exit(1)
254254
}
255+
if err = (&controller.HypervisorInstanceHaController{
256+
Client: mgr.GetClient(),
257+
Scheme: mgr.GetScheme(),
258+
}).SetupWithManager(mgr); err != nil {
259+
setupLog.Error(err, "unable to create controller", "controller", "HypervisorInstanceHa")
260+
os.Exit(1)
261+
}
255262

256263
if err = (&controller.NodeDecommissionReconciler{
257264
Client: mgr.GetClient(),

internal/controller/gardener_node_lifecycle_controller.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,12 @@ func (r *GardenerNodeLifecycleController) Reconcile(ctx context.Context, req ctr
9393
minAvailable = 0
9494

9595
if onboardingCompleted && isTerminating(node) {
96-
// Onboarded & terminating & eviction complete -> disable HA
97-
if err := disableInstanceHA(hv); err != nil {
98-
return ctrl.Result{}, err
96+
// Wait for HypervisorInstanceHa controller to disable HA
97+
if !meta.IsStatusConditionFalse(hv.Status.Conditions, kvmv1.ConditionTypeHaEnabled) {
98+
log.Info("waiting for HA to be disabled", "condition", kvmv1.ConditionTypeHaEnabled)
99+
return ctrl.Result{}, nil // Will be reconciled again when condition changes
99100
}
101+
log.Info("HA disabled successfully")
100102
}
101103
}
102104

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
SPDX-FileCopyrightText: Copyright 2024 SAP SE or an SAP affiliate company and cobaltcore-dev contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package controller
19+
20+
import (
21+
"context"
22+
"errors"
23+
24+
"k8s.io/apimachinery/pkg/api/equality"
25+
"k8s.io/apimachinery/pkg/api/meta"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/runtime"
28+
ctrl "sigs.k8s.io/controller-runtime"
29+
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
30+
logger "sigs.k8s.io/controller-runtime/pkg/log"
31+
32+
kvmv1 "github.com/cobaltcore-dev/openstack-hypervisor-operator/api/v1"
33+
)
34+
35+
const (
36+
HypervisorInstanceHaControllerName = "HypervisorInstanceHa"
37+
)
38+
39+
type HypervisorInstanceHaController struct {
40+
k8sclient.Client
41+
Scheme *runtime.Scheme
42+
}
43+
44+
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors,verbs=get;list;watch;update;patch
45+
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors/status,verbs=get;list;watch;update;patch
46+
func (r *HypervisorInstanceHaController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
47+
log := logger.FromContext(ctx).WithName(req.Name)
48+
ctx = logger.IntoContext(ctx, log)
49+
50+
hv := &kvmv1.Hypervisor{}
51+
if err := r.Get(ctx, req.NamespacedName, hv); err != nil {
52+
// ignore not found errors, could be deleted
53+
return ctrl.Result{}, k8sclient.IgnoreNotFound(err)
54+
}
55+
56+
if !meta.IsStatusConditionFalse(hv.Status.Conditions, kvmv1.ConditionTypeOnboarding) {
57+
// Hasn't completed onboarding
58+
return ctrl.Result{}, nil
59+
}
60+
61+
old := hv.DeepCopy()
62+
63+
// Determine if HA should be disabled and the reason
64+
evicted := meta.IsStatusConditionFalse(hv.Status.Conditions, kvmv1.ConditionTypeEvicting)
65+
shouldDisable := !hv.Spec.HighAvailability || evicted
66+
67+
if shouldDisable {
68+
// Determine the reason based on why HA is being disabled
69+
var reason string
70+
var message string
71+
if evicted {
72+
reason = kvmv1.ConditionReasonHaEvicted
73+
message = "HA disabled due to eviction"
74+
} else {
75+
reason = kvmv1.ConditionReasonSucceeded
76+
message = "HA disabled per spec"
77+
}
78+
79+
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
80+
Type: kvmv1.ConditionTypeHaEnabled,
81+
Status: metav1.ConditionFalse,
82+
Message: message,
83+
Reason: reason,
84+
}) {
85+
// Desired state already achieved
86+
return ctrl.Result{}, nil
87+
}
88+
89+
if err := disableInstanceHA(hv); err != nil {
90+
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
91+
Type: kvmv1.ConditionTypeHaEnabled,
92+
Status: metav1.ConditionUnknown,
93+
Message: err.Error(),
94+
Reason: kvmv1.ConditionReasonFailed,
95+
})
96+
97+
if patchErr := r.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(HypervisorInstanceHaControllerName)); patchErr != nil {
98+
return ctrl.Result{}, errors.Join(err, patchErr)
99+
}
100+
return ctrl.Result{}, err
101+
}
102+
} else {
103+
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
104+
Type: kvmv1.ConditionTypeHaEnabled,
105+
Status: metav1.ConditionTrue,
106+
Message: "HA is enabled",
107+
Reason: kvmv1.ConditionReasonSucceeded,
108+
}) {
109+
// Desired state already achieved
110+
return ctrl.Result{}, nil
111+
}
112+
113+
if err := enableInstanceHA(hv); err != nil {
114+
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
115+
Type: kvmv1.ConditionTypeHaEnabled,
116+
Status: metav1.ConditionUnknown,
117+
Message: err.Error(),
118+
Reason: kvmv1.ConditionReasonFailed,
119+
})
120+
121+
if patchErr := r.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(HypervisorInstanceHaControllerName)); patchErr != nil {
122+
return ctrl.Result{}, errors.Join(err, patchErr)
123+
}
124+
return ctrl.Result{}, err
125+
}
126+
}
127+
128+
if equality.Semantic.DeepEqual(hv, old) {
129+
return ctrl.Result{}, nil
130+
}
131+
132+
return ctrl.Result{}, r.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(HypervisorInstanceHaControllerName))
133+
}
134+
135+
// SetupWithManager sets up the controller with the Manager.
136+
func (r *HypervisorInstanceHaController) SetupWithManager(mgr ctrl.Manager) error {
137+
ctx := context.Background()
138+
_ = logger.FromContext(ctx)
139+
140+
return ctrl.NewControllerManagedBy(mgr).
141+
Named(HypervisorInstanceHaControllerName).
142+
For(&kvmv1.Hypervisor{}). // trigger the r.Reconcile whenever a hypervisor is created/updated/deleted.
143+
Complete(r)
144+
}

0 commit comments

Comments
 (0)