Skip to content

Commit a6999a0

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 a6999a0

File tree

5 files changed

+305
-12
lines changed

5 files changed

+305
-12
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

charts/openstack-hypervisor-operator/templates/manager-rbac.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,14 @@ rules:
4848
- kvm.cloud.sap
4949
resources:
5050
- evictions
51-
- hypervisors
52-
- hypervisors/status
5351
verbs:
5452
- create
5553
- delete
5654
- get
5755
- list
5856
- patch
5957
- update
58+
- verbs=get
6059
- watch
6160
- apiGroups:
6261
- kvm.cloud.sap
@@ -72,6 +71,19 @@ rules:
7271
- get
7372
- patch
7473
- update
74+
- apiGroups:
75+
- kvm.cloud.sap
76+
resources:
77+
- hypervisors
78+
- hypervisors/status
79+
verbs:
80+
- create
81+
- delete
82+
- get
83+
- list
84+
- patch
85+
- update
86+
- watch
7587
- apiGroups:
7688
- policy
7789
resources:

config/rbac/role.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,14 @@ rules:
4747
- kvm.cloud.sap
4848
resources:
4949
- evictions
50-
- hypervisors
51-
- hypervisors/status
5250
verbs:
5351
- create
5452
- delete
5553
- get
5654
- list
5755
- patch
5856
- update
57+
- verbs=get
5958
- watch
6059
- apiGroups:
6160
- kvm.cloud.sap
@@ -71,6 +70,19 @@ rules:
7170
- get
7271
- patch
7372
- update
73+
- apiGroups:
74+
- kvm.cloud.sap
75+
resources:
76+
- hypervisors
77+
- hypervisors/status
78+
verbs:
79+
- create
80+
- delete
81+
- get
82+
- list
83+
- patch
84+
- update
85+
- watch
7486
- apiGroups:
7587
- policy
7688
resources:

internal/controller/hypervisor_maintenance_controller.go

Lines changed: 115 additions & 3 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=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,15 +72,20 @@ 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

7578
changed, err := hec.reconcileComputeService(ctx, hv)
7679
if err != nil {
7780
return ctrl.Result{}, err
7881
}
7982

80-
if changed {
83+
changed1, err := hec.reconcileEviction(ctx, hv)
84+
if err != nil {
85+
return ctrl.Result{}, err
86+
}
87+
88+
if changed || changed1 {
8189
return ctrl.Result{}, hec.Status().Update(ctx, hv)
8290
} else {
8391
return ctrl.Result{}, nil
@@ -115,6 +123,9 @@ func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.
115123
return false, fmt.Errorf("failed to enable hypervisor due to %w", err)
116124
}
117125
case "manual", "auto", "ha": // Disable the compute service
126+
// Also in case of HA, as it doesn't hurt to disable it twice, and this
127+
// allows us to enable the service again, when the maintenance field is
128+
// cleared in the case above.
118129
if !meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
119130
Type: kvmv1.ConditionTypeHypervisorDisabled,
120131
Status: metav1.ConditionTrue,
@@ -147,6 +158,106 @@ func (hec *HypervisorMaintenanceController) reconcileComputeService(ctx context.
147158
return true, nil
148159
}
149160

161+
func (hec *HypervisorMaintenanceController) reconcileEviction(ctx context.Context, hv *kvmv1.Hypervisor) (bool, error) {
162+
eviction := &kvmv1.Eviction{
163+
ObjectMeta: metav1.ObjectMeta{
164+
Name: hv.Name,
165+
},
166+
}
167+
168+
switch hv.Spec.Maintenance {
169+
case "":
170+
// Avoid deleting the eviction over and over.
171+
if hv.Status.Evicted || meta.RemoveStatusCondition(&hv.Status.Conditions, kvmv1.ConditionTypeEvicting) {
172+
err := k8sclient.IgnoreNotFound(hec.Delete(ctx, eviction))
173+
hv.Status.Evicted = false
174+
return true, err
175+
}
176+
return false, nil
177+
case "manual", "auto": // 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 false, nil
182+
}
183+
}
184+
status, err := hec.ensureEviction(ctx, eviction, hv)
185+
if err != nil {
186+
return false, err
187+
}
188+
var reason, message string
189+
changed := false
190+
if status == metav1.ConditionFalse {
191+
message = "Evicted"
192+
reason = kvmv1.ConditionReasonSucceeded
193+
if !hv.Status.Evicted {
194+
changed = true
195+
hv.Status.Evicted = true
196+
}
197+
} else {
198+
message = "Evicting"
199+
reason = kvmv1.ConditionReasonRunning
200+
if hv.Status.Evicted {
201+
changed = true
202+
hv.Status.Evicted = false
203+
}
204+
}
205+
206+
if meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
207+
Type: kvmv1.ConditionTypeEvicting,
208+
Status: status,
209+
Reason: reason,
210+
Message: message,
211+
}) {
212+
changed = true
213+
}
214+
215+
if meta.SetStatusCondition(&hv.Status.Conditions, metav1.Condition{
216+
Type: kvmv1.ConditionTypeReady,
217+
Status: metav1.ConditionFalse,
218+
Reason: kvmv1.ConditionReasonReadyEvicted,
219+
Message: "Hypervisor is disabled and evicted",
220+
}) {
221+
changed = true
222+
}
223+
logger.FromContext(ctx).Info("reconcile", "changed", changed, "conditions", hv.Status.Conditions)
224+
return changed, nil
225+
}
226+
227+
return false, nil
228+
}
229+
230+
func (hec *HypervisorMaintenanceController) ensureEviction(ctx context.Context, eviction *kvmv1.Eviction, hypervisor *kvmv1.Hypervisor) (metav1.ConditionStatus, error) {
231+
log := logger.FromContext(ctx)
232+
if err := hec.Get(ctx, k8sclient.ObjectKeyFromObject(eviction), eviction); err != nil {
233+
if !k8serrors.IsNotFound(err) {
234+
return metav1.ConditionUnknown, fmt.Errorf("failed to get eviction due to %w", err)
235+
}
236+
if err := controllerutil.SetControllerReference(hypervisor, eviction, hec.Scheme); err != nil {
237+
return metav1.ConditionUnknown, err
238+
}
239+
log.Info("Creating new eviction", "name", eviction.Name)
240+
eviction.Spec = kvmv1.EvictionSpec{
241+
Hypervisor: hypervisor.Name,
242+
Reason: "openstack-hypervisor-operator maintenance",
243+
}
244+
245+
// This also transports the label-selector, if set
246+
transportLabels(&eviction.ObjectMeta, hypervisor)
247+
248+
if err = hec.Create(ctx, eviction); err != nil {
249+
return metav1.ConditionUnknown, fmt.Errorf("failed to create eviction due to %w", err)
250+
}
251+
}
252+
253+
// check if we are still evicting (defaulting to yes)
254+
if meta.IsStatusConditionFalse(eviction.Status.Conditions, kvmv1.ConditionTypeEvicting) {
255+
return metav1.ConditionFalse, nil
256+
} else {
257+
return metav1.ConditionTrue, nil
258+
}
259+
}
260+
150261
// SetupWithManager sets up the controller with the Manager.
151262
func (hec *HypervisorMaintenanceController) SetupWithManager(mgr ctrl.Manager) error {
152263
ctx := context.Background()
@@ -161,5 +272,6 @@ func (hec *HypervisorMaintenanceController) SetupWithManager(mgr ctrl.Manager) e
161272
return ctrl.NewControllerManagedBy(mgr).
162273
Named(HypervisorMaintenanceControllerName).
163274
For(&kvmv1.Hypervisor{}).
275+
Owns(&kvmv1.Eviction{}). // trigger Reconcile whenever an Own-ed eviction is created/updated/deleted
164276
Complete(hec)
165277
}

0 commit comments

Comments
 (0)