@@ -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
4144const (
42- HypervisorMaintenanceControllerName = "HypervisorMaintenanceController "
45+ HypervisorMaintenanceControllerName = "HypervisorMaintenance "
4346)
4447
4548type 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
5458func (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