Skip to content

Commit e6dc936

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 06d108d commit e6dc936

File tree

3 files changed

+278
-26
lines changed

3 files changed

+278
-26
lines changed

api/v1/hypervisor_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const (
3535
ConditionReasonReadyReady = "ready"
3636
// or not
3737
ConditionReasonReadyMaintenance = "maintenance"
38+
ConditionReasonReadyEvicted = "evicted"
3839
)
3940

4041
// HypervisorSpec defines the desired state of Hypervisor

internal/controller/hypervisor_maintenance_controller.go

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

27+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2728
"k8s.io/apimachinery/pkg/api/meta"
2829
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/runtime"
3031
ctrl "sigs.k8s.io/controller-runtime"
3132
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3234
logger "sigs.k8s.io/controller-runtime/pkg/log"
3335

3436
"github.com/gophercloud/gophercloud/v2"
@@ -39,7 +41,7 @@ import (
3941
)
4042

4143
const (
42-
HypervisorMaintenanceControllerName = "HypervisorMaintenanceController"
44+
HypervisorMaintenanceControllerName = "HypervisorMaintenance"
4345
)
4446

4547
type HypervisorMaintenanceController struct {
@@ -50,6 +52,7 @@ type HypervisorMaintenanceController struct {
5052

5153
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors,verbs=get;list;watch
5254
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=hypervisors/status,verbs=get;list;watch;create;update;patch;delete
55+
// +kubebuilder:rbac:groups=kvm.cloud.sap,resources=evictions,verbs=get;list;watch;create;update;patch;delete
5356

5457
func (hec *HypervisorMaintenanceController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
5558
hv := &kvmv1.Hypervisor{}
@@ -69,36 +72,40 @@ func (hec *HypervisorMaintenanceController) Reconcile(ctx context.Context, req c
6972
}
7073

7174
log := logger.FromContext(ctx).
72-
WithName("HypervisorService")
75+
WithName(HypervisorMaintenanceControllerName)
7376
ctx = logger.IntoContext(ctx, log)
7477

75-
changed, err := hec.reconcileComputeService(ctx, hv)
76-
if err != nil {
77-
return ctrl.Result{}, err
78-
}
78+
changed := false
79+
serviceId := hv.Status.ServiceID
7980

80-
if changed {
81-
return ctrl.Result{}, hec.Status().Update(ctx, hv)
82-
} else {
83-
return ctrl.Result{}, nil
81+
eviction := &kvmv1.Eviction{
82+
ObjectMeta: metav1.ObjectMeta{
83+
Name: hv.Name,
84+
},
8485
}
85-
}
86-
87-
func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.Context, hv *kvmv1.Hypervisor) (bool, error) {
88-
log := logger.FromContext(ctx)
89-
serviceId := hv.Status.ServiceID
9086

9187
switch hv.Spec.Maintenance {
92-
case "": // Enable the compute service (in case we haven't done so already)
88+
case "": // Cleanup
89+
// Update the eviction & status, but only if we have created it
90+
if hv.Status.Evicted || meta.RemoveStatusCondition(&hv.Status.Conditions, kvmv1.ConditionTypeEvicting) {
91+
if err := k8sclient.IgnoreNotFound(hec.Delete(ctx, eviction)); err != nil {
92+
return ctrl.Result{}, err
93+
}
94+
hv.Status.Evicted = false
95+
changed = true
96+
}
97+
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+
break
101107
}
108+
changed = true
102109

103110
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
104111
Type: kvmv1.ConditionTypeReady,
@@ -112,18 +119,22 @@ func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.
112119
log.Info("Enabling hypervisor", "id", serviceId)
113120
_, err := services.Update(ctx, hec.computeClient, serviceId, enableService).Extract()
114121
if err != nil {
115-
return false, fmt.Errorf("failed to enable hypervisor due to %w", err)
122+
return ctrl.Result{}, fmt.Errorf("failed to enable hypervisor due to %w", err)
116123
}
117124
case "manual", "auto", "ha": // 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+
break
126136
}
137+
changed = true
127138

128139
meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
129140
Type: kvmv1.ConditionTypeReady,
@@ -140,11 +151,94 @@ func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.
140151
log.Info("Disabling hypervisor", "id", serviceId)
141152
_, err := services.Update(ctx, hec.computeClient, serviceId, enableService).Extract()
142153
if err != nil {
143-
return false, fmt.Errorf("failed to disable hypervisor due to %w", err)
154+
return ctrl.Result{}, fmt.Errorf("failed to disable hypervisor due to %w", err)
144155
}
145156
}
146157

147-
return true, nil
158+
switch hv.Spec.Maintenance {
159+
case "manual", "auto": // In case of "ha", the host gets emptied from the HA service
160+
if cond := meta.FindStatusCondition(hv.Status.Conditions, kvmv1.ConditionTypeEvicting); cond != nil {
161+
if cond.Reason == kvmv1.ConditionReasonSucceeded {
162+
// We are done here, no need to look at the eviction any more
163+
return ctrl.Result{}, nil
164+
}
165+
}
166+
status, err := hec.ensureEviction(ctx, eviction, hv)
167+
if err != nil {
168+
return ctrl.Result{}, err
169+
}
170+
var reason, message string
171+
if status == metav1.ConditionFalse {
172+
message = "Evicted"
173+
reason = kvmv1.ConditionReasonSucceeded
174+
if !hv.Status.Evicted {
175+
changed = true
176+
hv.Status.Evicted = true
177+
}
178+
} else {
179+
message = "Evicting"
180+
reason = kvmv1.ConditionReasonRunning
181+
if hv.Status.Evicted {
182+
changed = true
183+
hv.Status.Evicted = false
184+
}
185+
}
186+
187+
if meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
188+
Type: kvmv1.ConditionTypeEvicting,
189+
Status: status,
190+
Reason: reason,
191+
Message: message,
192+
}) {
193+
changed = true
194+
}
195+
196+
if meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
197+
Type: kvmv1.ConditionTypeReady,
198+
Status: metav1.ConditionFalse,
199+
Reason: kvmv1.ConditionReasonReadyEvicted,
200+
Message: "Hypervisor is disabled and evicted",
201+
}) {
202+
changed = true
203+
}
204+
}
205+
206+
if changed {
207+
return ctrl.Result{}, hec.Status().Update(ctx, hv)
208+
} else {
209+
return ctrl.Result{}, nil
210+
}
211+
}
212+
213+
func (hec *HypervisorMaintenanceController) ensureEviction(ctx context.Context, eviction *kvmv1.Eviction, hypervisor *kvmv1.Hypervisor) (metav1.ConditionStatus, error) {
214+
log := logger.FromContext(ctx)
215+
if err := hec.Get(ctx, k8sclient.ObjectKeyFromObject(eviction), eviction); err != nil {
216+
if !k8serrors.IsNotFound(err) {
217+
return metav1.ConditionUnknown, fmt.Errorf("failed to get eviction due to %w", err)
218+
}
219+
if err := controllerutil.SetControllerReference(hypervisor, eviction, hec.Scheme); err != nil {
220+
return metav1.ConditionUnknown, err
221+
}
222+
log.Info("Creating new eviction", "name", eviction.Name)
223+
eviction.Spec = kvmv1.EvictionSpec{
224+
Hypervisor: hypervisor.Name,
225+
Reason: "openstack-hypervisor-operator maintenance",
226+
}
227+
228+
// This also transports the label-selector, if set
229+
transportLabels(&eviction.ObjectMeta, hypervisor)
230+
231+
if err = hec.Create(ctx, eviction); err != nil {
232+
return metav1.ConditionUnknown, fmt.Errorf("failed to create eviction due to %w", err)
233+
}
234+
}
235+
236+
// check if we are still evicting (defaulting to yes)
237+
if meta.IsStatusConditionFalse(eviction.Status.Conditions, kvmv1.ConditionTypeEvicting) {
238+
return metav1.ConditionFalse, nil
239+
} else {
240+
return metav1.ConditionTrue, nil
241+
}
148242
}
149243

150244
// SetupWithManager sets up the controller with the Manager.
@@ -161,5 +255,6 @@ func (hec *HypervisorMaintenanceController) SetupWithManager(mgr ctrl.Manager) e
161255
return ctrl.NewControllerManagedBy(mgr).
162256
Named(HypervisorMaintenanceControllerName).
163257
For(&kvmv1.Hypervisor{}).
258+
Owns(&kvmv1.Eviction{}). // trigger Reconcile whenever an Own-ed eviction is created/updated/deleted
164259
Complete(hec)
165260
}

0 commit comments

Comments
 (0)