Skip to content

Commit 64f9483

Browse files
committed
HypervisorMaintenance: Trigger eviction based on maintenance flag
When maintenance is set, it will trigger an eviction via the respective CRD and reflect the result back to the hypervisor status.
1 parent 1b57ee1 commit 64f9483

File tree

3 files changed

+287
-22
lines changed

3 files changed

+287
-22
lines changed

api/v1/hypervisor_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ const (
3535
ConditionReasonReadyReady = "ready"
3636
// or not
3737
ConditionReasonReadyMaintenance = "maintenance"
38+
ConditionReasonReadyEvicted = "evicted"
39+
40+
// HypervisorMaintenance "enum"
41+
MaintenanceUnset = ""
42+
MaintenanceManual = "manual"
43+
MaintenanceAuto = "auto"
44+
MaintenanceHA = "ha"
3845
)
3946

4047
// HypervisorSpec defines the desired state of Hypervisor

internal/controller/hypervisor_maintenance_controller.go

Lines changed: 119 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ import (
2424
"context"
2525
"fmt"
2626

27+
"k8s.io/apimachinery/pkg/api/equality"
28+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2729
"k8s.io/apimachinery/pkg/api/meta"
2830
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2931
"k8s.io/apimachinery/pkg/runtime"
3032
ctrl "sigs.k8s.io/controller-runtime"
3133
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3235
logger "sigs.k8s.io/controller-runtime/pkg/log"
3336

3437
"github.com/gophercloud/gophercloud/v2"
@@ -39,7 +42,7 @@ import (
3942
)
4043

4144
const (
42-
HypervisorMaintenanceControllerName = "HypervisorMaintenanceController"
45+
HypervisorMaintenanceControllerName = "HypervisorMaintenance"
4346
)
4447

4548
type HypervisorMaintenanceController struct {
@@ -50,6 +53,7 @@ type HypervisorMaintenanceController struct {
5053

5154
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors,verbs=get;list;watch
5255
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors/status,verbs=get;list;watch;create;update;patch;delete
56+
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=evictions,verbs=get;list;watch;create;update;patch;delete
5357

5458
func (hec *HypervisorMaintenanceController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
5559
hv := &kvmv1.Hypervisor{}
@@ -68,36 +72,38 @@ func (hec *HypervisorMaintenanceController) Reconcile(ctx context.Context, req c
6872
return ctrl.Result{}, nil
6973
}
7074

71-
log := logger.FromContext(ctx).
72-
WithName("HypervisorService")
73-
ctx = logger.IntoContext(ctx, log)
75+
old := hv.DeepCopy()
7476

75-
changed, err := hec.reconcileComputeService(ctx, hv)
76-
if err != nil {
77+
if err := hec.reconcileComputeService(ctx, hv); err != nil {
7778
return ctrl.Result{}, err
7879
}
7980

80-
if changed {
81-
return ctrl.Result{}, hec.Status().Update(ctx, hv)
82-
} else {
81+
if err := hec.reconcileEviction(ctx, hv); err != nil {
82+
return ctrl.Result{}, err
83+
}
84+
85+
if equality.Semantic.DeepEqual(hv, old) {
8386
return ctrl.Result{}, nil
8487
}
88+
89+
return ctrl.Result{}, hec.Status().Patch(ctx, hv, k8sclient.MergeFromWithOptions(old, k8sclient.MergeFromWithOptimisticLock{}))
8590
}
8691

87-
func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.Context, hv *kvmv1.Hypervisor) (bool, error) {
92+
func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.Context, hv *kvmv1.Hypervisor) error {
8893
log := logger.FromContext(ctx)
8994
serviceId := hv.Status.ServiceID
9095

9196
switch hv.Spec.Maintenance {
92-
case "": // Enable the compute service (in case we haven't done so already)
97+
case kvmv1.MaintenanceUnset:
98+
// Enable the compute service (in case we haven't done so already)
9399
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
94100
Type: kvmv1.ConditionTypeHypervisorDisabled,
95101
Status: metav1.ConditionFalse,
96102
Message: "Hypervisor is enabled",
97103
Reason: kvmv1.ConditionReasonSucceeded,
98104
}) {
99105
// Spec matches status
100-
return false, nil
106+
return nil
101107
}
102108

103109
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
@@ -112,17 +118,21 @@ func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.
112118
log.Info("Enabling hypervisor", "id", serviceId)
113119
_, err := services.Update(ctx, hec.computeClient, serviceId, enableService).Extract()
114120
if err != nil {
115-
return false, fmt.Errorf("failed to enable hypervisor due to %w", err)
121+
return fmt.Errorf("failed to enable hypervisor due to %w", err)
116122
}
117-
case "manual", "auto", "ha": // Disable the compute service
123+
case kvmv1.MaintenanceManual, kvmv1.MaintenanceAuto, kvmv1.MaintenanceHA:
124+
// Disable the compute service:
125+
// Also in case of HA, as it doesn't hurt to disable it twice, and this
126+
// allows us to enable the service again, when the maintenance field is
127+
// cleared in the case above.
118128
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
119129
Type: kvmv1.ConditionTypeHypervisorDisabled,
120130
Status: metav1.ConditionTrue,
121131
Message: "Hypervisor is disabled",
122132
Reason: kvmv1.ConditionReasonSucceeded,
123133
}) {
124134
// Spec matches status
125-
return false, nil
135+
return nil
126136
}
127137

128138
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
@@ -140,11 +150,102 @@ func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.
140150
log.Info("Disabling hypervisor", "id", serviceId)
141151
_, err := services.Update(ctx, hec.computeClient, serviceId, enableService).Extract()
142152
if err != nil {
143-
return false, fmt.Errorf("failed to disable hypervisor due to %w", err)
153+
return fmt.Errorf("failed to disable hypervisor due to %w", err)
154+
}
155+
}
156+
157+
return nil
158+
}
159+
160+
func (hec *HypervisorMaintenanceController) reconcileEviction(ctx context.Context, hv *kvmv1.Hypervisor) error {
161+
eviction := &kvmv1.Eviction{
162+
ObjectMeta: metav1.ObjectMeta{
163+
Name: hv.Name,
164+
},
165+
}
166+
167+
switch hv.Spec.Maintenance {
168+
case kvmv1.MaintenanceUnset:
169+
// Avoid deleting the eviction over and over.
170+
if hv.Status.Evicted || meta.RemoveStatusCondition(&hv.Status.Conditions, kvmv1.ConditionTypeEvicting) {
171+
err := k8sclient.IgnoreNotFound(hec.Delete(ctx, eviction))
172+
hv.Status.Evicted = false
173+
return err
174+
}
175+
return nil
176+
case kvmv1.MaintenanceManual, kvmv1.MaintenanceAuto:
177+
// In case of "ha", the host gets emptied from the HA service
178+
if cond := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeEvicting); cond != nil {
179+
if cond.Reason == kvmv1.ConditionReasonSucceeded {
180+
// We are done here, no need to look at the eviction any more
181+
return nil
182+
}
183+
}
184+
status, err := hec.ensureEviction(ctx, eviction, hv)
185+
if err != nil {
186+
return err
187+
}
188+
var reason, message string
189+
190+
if status == metav1.ConditionFalse {
191+
message = "Evicted"
192+
reason = kvmv1.ConditionReasonSucceeded
193+
hv.Status.Evicted = true
194+
} else {
195+
message = "Evicting"
196+
reason = kvmv1.ConditionReasonRunning
197+
hv.Status.Evicted = false
198+
}
199+
200+
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
201+
Type: kvmv1.ConditionTypeEvicting,
202+
Status: status,
203+
Reason: reason,
204+
Message: message,
205+
})
206+
207+
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
208+
Type: kvmv1.ConditionTypeReady,
209+
Status: metav1.ConditionFalse,
210+
Reason: kvmv1.ConditionReasonReadyEvicted,
211+
Message: "Hypervisor is disabled and evicted",
212+
})
213+
214+
return nil
215+
}
216+
217+
return nil
218+
}
219+
220+
func (hec *HypervisorMaintenanceController) ensureEviction(ctx context.Context, eviction *kvmv1.Eviction, hypervisor *kvmv1.Hypervisor) (metav1.ConditionStatus, error) {
221+
log := logger.FromContext(ctx)
222+
if err := hec.Get(ctx, k8sclient.ObjectKeyFromObject(eviction), eviction); err != nil {
223+
if !k8serrors.IsNotFound(err) {
224+
return metav1.ConditionUnknown, fmt.Errorf("failed to get eviction due to %w", err)
225+
}
226+
if err := controllerutil.SetControllerReference(hypervisor, eviction, hec.Scheme); err != nil {
227+
return metav1.ConditionUnknown, err
228+
}
229+
log.Info("Creating new eviction", "name", eviction.Name)
230+
eviction.Spec = kvmv1.EvictionSpec{
231+
Hypervisor: hypervisor.Name,
232+
Reason: "openstack-hypervisor-operator maintenance",
233+
}
234+
235+
// This also transports the label-selector, if set
236+
transportLabels(&eviction.ObjectMeta, hypervisor)
237+
238+
if err = hec.Create(ctx, eviction); err != nil {
239+
return metav1.ConditionUnknown, fmt.Errorf("failed to create eviction due to %w", err)
144240
}
145241
}
146242

147-
return true, nil
243+
// check if we are still evicting (defaulting to yes)
244+
if meta.IsStatusConditionFalse(eviction.Status.Conditions, kvmv1.ConditionTypeEvicting) {
245+
return metav1.ConditionFalse, nil
246+
} else {
247+
return metav1.ConditionTrue, nil
248+
}
148249
}
149250

150251
// SetupWithManager sets up the controller with the Manager.
@@ -161,5 +262,6 @@ func (hec *HypervisorMaintenanceController) SetupWithManager(mgr ctrl.Manager) e
161262
return ctrl.NewControllerManagedBy(mgr).
162263
Named(HypervisorMaintenanceControllerName).
163264
For(&kvmv1.Hypervisor{}).
265+
Owns(&kvmv1.Eviction{}). // trigger Reconcile whenever an Own-ed eviction is created/updated/deleted
164266
Complete(hec)
165267
}

0 commit comments

Comments
 (0)