Skip to content

Commit e58bb18

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 9c8ed61 commit e58bb18

9 files changed

+568
-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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ const (
7272
ConditionReasonTestAggregates = "TestAggregates"
7373
ConditionReasonTerminating = "Terminating"
7474
ConditionReasonEvictionInProgress = "EvictionInProgress"
75+
76+
// ConditionTypeHaEnabled reasons
77+
ConditionReasonHaEvicted = "Evicted" // HA disabled due to eviction
78+
ConditionReasonHaOnboarding = "Onboarding" // HA disabled during onboarding
7579
)
7680

7781
// 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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ 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+
return ctrl.Result{}, nil // Will be reconciled again when condition changes
9999
}
100100
}
101101
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
onboardingCondition := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeOnboarding)
57+
58+
if onboardingCondition == nil {
59+
return ctrl.Result{}, nil // Onboarding not started, so no hypervisor and nothing to do
60+
}
61+
62+
old := hv.DeepCopy()
63+
64+
// Determine if HA should be disabled and the reason
65+
evicted := meta.IsStatusConditionFalse(hv.Status.Conditions, kvmv1.ConditionTypeEvicting)
66+
testing := onboardingCondition.Status == metav1.ConditionTrue &&
67+
onboardingCondition.Reason != kvmv1.ConditionReasonHandover // Onboarding still testing (not yet in Handover phase)
68+
aborted := onboardingCondition.Status == metav1.ConditionFalse &&
69+
onboardingCondition.Reason == kvmv1.ConditionReasonAborted // Onboarding was aborted
70+
shouldDisable := !hv.Spec.HighAvailability || // HA not requested
71+
evicted || // HA not needed as it is empty
72+
testing || // HA not needed for test VMs
73+
aborted // HA not needed when onboarding aborted
74+
75+
if shouldDisable {
76+
// Determine the reason based on why HA is being disabled
77+
var reason string
78+
var message string
79+
switch {
80+
case evicted:
81+
reason = kvmv1.ConditionReasonHaEvicted
82+
message = "HA disabled due to eviction"
83+
case testing, aborted:
84+
reason = kvmv1.ConditionReasonHaOnboarding
85+
message = "HA disabled before onboarding"
86+
default:
87+
reason = kvmv1.ConditionReasonSucceeded
88+
message = "HA disabled per spec"
89+
}
90+
91+
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
92+
Type: kvmv1.ConditionTypeHaEnabled,
93+
Status: metav1.ConditionFalse,
94+
Message: message,
95+
Reason: reason,
96+
}) {
97+
// Desired state already achieved
98+
return ctrl.Result{}, nil
99+
}
100+
101+
if err := disableInstanceHA(hv); err != nil {
102+
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
103+
Type: kvmv1.ConditionTypeHaEnabled,
104+
Status: metav1.ConditionUnknown,
105+
Message: err.Error(),
106+
Reason: kvmv1.ConditionReasonFailed,
107+
})
108+
109+
if patchErr := r.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(HypervisorInstanceHaControllerName)); patchErr != nil {
110+
return ctrl.Result{}, errors.Join(err, patchErr)
111+
}
112+
return ctrl.Result{}, err
113+
}
114+
} else {
115+
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
116+
Type: kvmv1.ConditionTypeHaEnabled,
117+
Status: metav1.ConditionTrue,
118+
Message: "HA is enabled",
119+
Reason: kvmv1.ConditionReasonSucceeded,
120+
}) {
121+
// Desired state already achieved
122+
return ctrl.Result{}, nil
123+
}
124+
125+
if err := enableInstanceHA(hv); err != nil {
126+
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
127+
Type: kvmv1.ConditionTypeHaEnabled,
128+
Status: metav1.ConditionUnknown,
129+
Message: err.Error(),
130+
Reason: kvmv1.ConditionReasonFailed,
131+
})
132+
133+
if patchErr := r.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(HypervisorInstanceHaControllerName)); patchErr != nil {
134+
return ctrl.Result{}, errors.Join(err, patchErr)
135+
}
136+
return ctrl.Result{}, err
137+
}
138+
}
139+
140+
if equality.Semantic.DeepEqual(hv, old) {
141+
return ctrl.Result{}, nil
142+
}
143+
144+
return ctrl.Result{}, r.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}), k8sclient.FieldOwner(HypervisorInstanceHaControllerName))
145+
}
146+
147+
// SetupWithManager sets up the controller with the Manager.
148+
func (r *HypervisorInstanceHaController) SetupWithManager(mgr ctrl.Manager) error {
149+
ctx := context.Background()
150+
_ = logger.FromContext(ctx)
151+
152+
return ctrl.NewControllerManagedBy(mgr).
153+
Named(HypervisorInstanceHaControllerName).
154+
For(&kvmv1.Hypervisor{}). // trigger the r.Reconcile whenever a hypervisor is created/updated/deleted.
155+
Complete(r)
156+
}

0 commit comments

Comments
 (0)