@@ -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=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,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