@@ -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
4143const (
42- HypervisorMaintenanceControllerName = "HypervisorMaintenanceController "
44+ HypervisorMaintenanceControllerName = "HypervisorMaintenance "
4345)
4446
4547type 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
5457func (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.
151262func (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