Skip to content

Commit 56b3698

Browse files
committed
HypervisorController: Do not overwrite evicting
Both the eviction controller as well as the hypervisor controller are writing the summary condition, leading to the condition flipping, depending on who has written last. Let's give the eviction controller priority as there are more states (Evicting, Evicted) instead of just ScaleDown.
1 parent 8267927 commit 56b3698

2 files changed

Lines changed: 132 additions & 39 deletions

File tree

internal/controller/hypervisor_controller.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,18 @@ func (hv *HypervisorController) Reconcile(ctx context.Context, req ctrl.Request)
112112
nodeTerminationCondition := FindNodeStatusCondition(node.Status.Conditions, "Terminating")
113113
if nodeTerminationCondition != nil && nodeTerminationCondition.Status == corev1.ConditionTrue {
114114
// Node might be terminating, propagate condition to hypervisor
115-
meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{
116-
Type: kvmv1.ConditionTypeReady,
117-
Status: metav1.ConditionFalse,
118-
Reason: nodeTerminationCondition.Reason,
119-
Message: nodeTerminationCondition.Message,
120-
})
115+
116+
if readyCondition := meta.FindStatusCondition(hypervisor.Status.Conditions, kvmv1.ConditionTypeReady); readyCondition == nil || readyCondition.Status == metav1.ConditionTrue {
117+
// Only set Terminating condition if Ready is still True, otherwise we might overwrite other controllers that already set Ready to False
118+
// In particular if the hypervisor is evicting
119+
meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{
120+
Type: kvmv1.ConditionTypeReady,
121+
Status: metav1.ConditionFalse,
122+
Reason: nodeTerminationCondition.Reason,
123+
Message: nodeTerminationCondition.Message,
124+
})
125+
}
126+
121127
meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{
122128
Type: kvmv1.ConditionTypeTerminating,
123129
Status: metav1.ConditionStatus(nodeTerminationCondition.Status),

internal/controller/hypervisor_controller_test.go

Lines changed: 120 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ import (
3232

3333
var _ = Describe("Hypervisor Controller", func() {
3434
const (
35-
resourceName = "other-node"
36-
topologyZone = "test-zone"
37-
customTrait = "test-trait"
38-
aggregate1 = "aggregate1"
39-
workerGroupLabel = "worker.garden.sapcloud.io/group"
40-
workerGroupValue = "test-group"
35+
resourceName = "other-node"
36+
topologyZone = "test-zone"
37+
customTrait = "test-trait"
38+
aggregate1 = "aggregate1"
39+
workerGroupLabel = "worker.garden.sapcloud.io/group"
40+
workerGroupValue = "test-group"
41+
terminatingReason = "ScaleDown"
4142
)
4243
var (
4344
hypervisorController *HypervisorController
@@ -264,7 +265,7 @@ var _ = Describe("Hypervisor Controller", func() {
264265
})
265266

266267
Context("When reconciling a terminating node", func() {
267-
It("should successfully reconcile the terminating node", func(ctx SpecContext) {
268+
BeforeEach(func(ctx SpecContext) {
268269
// Mark the node as terminating
269270
resource.Finalizers = append(resource.Finalizers, "example.com/test-finalizer")
270271
Expect(k8sClient.Update(ctx, resource)).To(Succeed())
@@ -281,42 +282,128 @@ var _ = Describe("Hypervisor Controller", func() {
281282
resource.Status.Conditions = append(resource.Status.Conditions, corev1.NodeCondition{
282283
Type: "Terminating",
283284
Status: corev1.ConditionTrue,
284-
Reason: "Terminating",
285+
Reason: terminatingReason,
285286
Message: "Node is terminating",
286287
})
287288
Expect(k8sClient.Status().Update(ctx, resource)).To(Succeed())
288289

289-
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
290-
NamespacedName: types.NamespacedName{Name: resource.Name},
291-
})
292-
Expect(err).NotTo(HaveOccurred())
293-
hypervisor := &kvmv1.Hypervisor{}
294-
Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
295-
Expect(hypervisor.Spec.Maintenance).To(Equal(kvmv1.MaintenanceTermination))
290+
})
296291

297-
By("Reconciling the created resource")
298-
for range 2 {
292+
Context("and the Hypervisor resource does not exists", func() {
293+
It("should successfully reconcile the terminating node", func(ctx SpecContext) {
299294
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
300295
NamespacedName: types.NamespacedName{Name: resource.Name},
301296
})
302297
Expect(err).NotTo(HaveOccurred())
303-
}
298+
hypervisor := &kvmv1.Hypervisor{}
299+
Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
300+
Expect(hypervisor.Spec.Maintenance).To(Equal(kvmv1.MaintenanceTermination))
301+
302+
By("Reconciling the created resource")
303+
for range 2 {
304+
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
305+
NamespacedName: types.NamespacedName{Name: resource.Name},
306+
})
307+
Expect(err).NotTo(HaveOccurred())
308+
}
304309

305-
By("should have set the terminating condition on the Hypervisor resource")
306-
// Get the Hypervisor resource
307-
updatedHypervisor := &kvmv1.Hypervisor{}
308-
Expect(hypervisorController.Get(ctx, hypervisorName, updatedHypervisor)).To(Succeed())
309-
Expect(updatedHypervisor.Name).To(Equal(resource.Name))
310-
Expect(updatedHypervisor.Status.Conditions).ToNot(BeNil())
311-
condition := meta.FindStatusCondition(updatedHypervisor.Status.Conditions, kvmv1.ConditionTypeReady)
312-
Expect(condition).ToNot(BeNil())
313-
Expect(condition.Reason).To(Equal("Terminating"))
314-
Expect(condition.Status).To(Equal(metav1.ConditionFalse))
315-
316-
condition = meta.FindStatusCondition(updatedHypervisor.Status.Conditions, kvmv1.ConditionTypeTerminating)
317-
Expect(condition).ToNot(BeNil())
318-
Expect(condition.Reason).To(Equal("Terminating"))
319-
Expect(condition.Status).To(Equal(metav1.ConditionTrue))
310+
By("should have set the terminating condition on the Hypervisor resource")
311+
// Get the Hypervisor resource
312+
updatedHypervisor := &kvmv1.Hypervisor{}
313+
Expect(hypervisorController.Get(ctx, hypervisorName, updatedHypervisor)).To(Succeed())
314+
Expect(updatedHypervisor.Status.Conditions).To(ContainElements(
315+
SatisfyAll(
316+
HaveField("Type", kvmv1.ConditionTypeReady),
317+
HaveField("Reason", terminatingReason),
318+
HaveField("Status", metav1.ConditionFalse),
319+
),
320+
SatisfyAll(
321+
HaveField("Type", kvmv1.ConditionTypeTerminating),
322+
HaveField("Reason", terminatingReason),
323+
HaveField("Status", metav1.ConditionTrue),
324+
),
325+
))
326+
})
327+
})
328+
329+
Context("and the Hypervisor resource does exists", func() {
330+
BeforeEach(func(ctx SpecContext) {
331+
hypervisor := &kvmv1.Hypervisor{
332+
ObjectMeta: metav1.ObjectMeta{
333+
Name: resource.Name,
334+
},
335+
Spec: kvmv1.HypervisorSpec{
336+
Maintenance: kvmv1.MaintenanceUnset,
337+
},
338+
}
339+
Expect(k8sClient.Create(ctx, hypervisor)).To(Succeed())
340+
DeferCleanup(func(ctx SpecContext) {
341+
Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, hypervisor))).To(Succeed())
342+
})
343+
})
344+
345+
Context("and the Hypervisor resource already has a Ready Condition set to false", func() {
346+
BeforeEach(func(ctx SpecContext) {
347+
hypervisor := &kvmv1.Hypervisor{}
348+
Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed())
349+
meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{
350+
Type: kvmv1.ConditionTypeReady,
351+
Status: metav1.ConditionFalse,
352+
Reason: "SomeOtherReason",
353+
})
354+
Expect(k8sClient.Status().Update(ctx, hypervisor)).To(Succeed())
355+
})
356+
357+
It("should not update the existing Ready Condition with the new reason", func(ctx SpecContext) {
358+
for range 2 {
359+
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
360+
NamespacedName: types.NamespacedName{Name: resource.Name},
361+
})
362+
Expect(err).NotTo(HaveOccurred())
363+
}
364+
365+
// Get the Hypervisor resource again
366+
updatedHypervisor := &kvmv1.Hypervisor{}
367+
Expect(k8sClient.Get(ctx, hypervisorName, updatedHypervisor)).To(Succeed())
368+
Expect(updatedHypervisor.Status.Conditions).To(ContainElement(
369+
SatisfyAll(
370+
HaveField("Type", kvmv1.ConditionTypeReady),
371+
HaveField("Reason", "SomeOtherReason"),
372+
HaveField("Status", metav1.ConditionFalse),
373+
),
374+
))
375+
})
376+
})
377+
378+
It("should successfully reconcile the terminating node", func(ctx SpecContext) {
379+
By("Reconciling the created resource")
380+
for range 2 {
381+
_, err := hypervisorController.Reconcile(ctx, ctrl.Request{
382+
NamespacedName: types.NamespacedName{Name: resource.Name},
383+
})
384+
Expect(err).NotTo(HaveOccurred())
385+
}
386+
387+
By("should have set the terminating condition on the Hypervisor resource")
388+
// Get the Hypervisor resource
389+
updatedHypervisor := &kvmv1.Hypervisor{}
390+
Expect(hypervisorController.Get(ctx, hypervisorName, updatedHypervisor)).To(Succeed())
391+
// Not sure, if that is a good idea, but that is the current behaviour
392+
// We expect another operator to set the Maintenance field to Termination
393+
Expect(updatedHypervisor.Spec.Maintenance).NotTo(Equal(kvmv1.MaintenanceTermination))
394+
Expect(updatedHypervisor.Status.Conditions).To(ContainElements(
395+
SatisfyAll(
396+
HaveField("Type", kvmv1.ConditionTypeReady),
397+
HaveField("Reason", terminatingReason),
398+
HaveField("Status", metav1.ConditionFalse),
399+
),
400+
SatisfyAll(
401+
HaveField("Type", kvmv1.ConditionTypeTerminating),
402+
HaveField("Reason", terminatingReason),
403+
HaveField("Status", metav1.ConditionTrue),
404+
),
405+
))
406+
})
320407
})
321408
})
322409
})

0 commit comments

Comments
 (0)