diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go index baf8301..4af9043 100644 --- a/api/v1/hypervisor_types.go +++ b/api/v1/hypervisor_types.go @@ -60,7 +60,9 @@ const ( // ConditionTypeReady is the type of condition for ready status of a hypervisor ConditionTypeReady = "Ready" - // ConditionTypeTerminating is the type of condition for terminating status of a hypervisor + // ConditionTypeTerminating is set when the underlying Node is terminating — + // either because the legacy gardener node condition "Terminating" is present, + // or because the Node's deletionTimestamp has been set (gardener's future path). ConditionTypeTerminating = "Terminating" // ConditionTypeTainted is the type of condition for tainted status of a hypervisor diff --git a/internal/controller/hypervisor_controller.go b/internal/controller/hypervisor_controller.go index 0ee5730..d174eb6 100644 --- a/internal/controller/hypervisor_controller.go +++ b/internal/controller/hypervisor_controller.go @@ -130,15 +130,16 @@ func (hv *HypervisorController) Reconcile(ctx context.Context, req ctrl.Request) } } - // update terminating condition + // update terminating condition — fired by either the legacy gardener + // node condition or the node's deletion timestamp (gardener's future path) nodeTerminationCondition := FindNodeStatusCondition(node.Status.Conditions, "Terminating") - if nodeTerminationCondition != nil && nodeTerminationCondition.Status == corev1.ConditionTrue { - // Node might be terminating, propagate condition to hypervisor + if (nodeTerminationCondition != nil && nodeTerminationCondition.Status == corev1.ConditionTrue) || + !node.DeletionTimestamp.IsZero() { meta.SetStatusCondition(&hypervisor.Status.Conditions, metav1.Condition{ Type: kvmv1.ConditionTypeTerminating, - Status: metav1.ConditionStatus(nodeTerminationCondition.Status), - Reason: nodeTerminationCondition.Reason, - Message: nodeTerminationCondition.Message, + Status: metav1.ConditionTrue, + Reason: kvmv1.ConditionReasonTerminating, + Message: "Node is terminating", }) } @@ -188,7 +189,8 @@ func (hv *HypervisorController) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, fmt.Errorf("failed setting controller reference: %w", err) } - if IsNodeConditionPresentAndEqual(node.Status.Conditions, "Terminating", corev1.ConditionTrue) { + if IsNodeConditionPresentAndEqual(node.Status.Conditions, "Terminating", corev1.ConditionTrue) || + !node.DeletionTimestamp.IsZero() { hypervisor.Spec.Maintenance = kvmv1.MaintenanceTermination } diff --git a/internal/controller/hypervisor_controller_test.go b/internal/controller/hypervisor_controller_test.go index 92313ad..7c89e97 100644 --- a/internal/controller/hypervisor_controller_test.go +++ b/internal/controller/hypervisor_controller_test.go @@ -318,13 +318,12 @@ var _ = Describe("Hypervisor Controller", func() { } By("should have set the terminating condition on the Hypervisor resource") - // Get the Hypervisor resource updatedHypervisor := &kvmv1.Hypervisor{} Expect(hypervisorController.Get(ctx, hypervisorName, updatedHypervisor)).To(Succeed()) Expect(updatedHypervisor.Status.Conditions).To(ContainElement( SatisfyAll( HaveField("Type", kvmv1.ConditionTypeTerminating), - HaveField("Reason", terminatingReason), + HaveField("Reason", kvmv1.ConditionReasonTerminating), HaveField("Status", metav1.ConditionTrue), ), )) @@ -366,8 +365,42 @@ var _ = Describe("Hypervisor Controller", func() { Expect(updatedHypervisor.Status.Conditions).To(ContainElement( SatisfyAll( HaveField("Type", kvmv1.ConditionTypeTerminating), - HaveField("Reason", terminatingReason), + HaveField("Reason", kvmv1.ConditionReasonTerminating), + HaveField("Status", metav1.ConditionTrue), + ), + )) + }) + }) + Context("when only the deletion timestamp is set (no Terminating node condition)", func() { + BeforeEach(func(ctx SpecContext) { + // Use a different node that has no Terminating condition — only + // a deletion timestamp — to verify the new gardener signal path. + resource.Status.Conditions = nil + Expect(k8sClient.Status().Update(ctx, resource)).To(Succeed()) + + By("creating a Hypervisor so the existing-hypervisor path is exercised") + hypervisor := &kvmv1.Hypervisor{ + ObjectMeta: metav1.ObjectMeta{Name: resource.Name}, + } + Expect(k8sClient.Create(ctx, hypervisor)).To(Succeed()) + DeferCleanup(func(ctx SpecContext) { + Expect(client.IgnoreNotFound(k8sClient.Delete(ctx, hypervisor))).To(Succeed()) + }) + }) + + It("should set the Terminating condition from the deletion timestamp", func(ctx SpecContext) { + _, err := hypervisorController.Reconcile(ctx, ctrl.Request{ + NamespacedName: types.NamespacedName{Name: resource.Name}, + }) + Expect(err).NotTo(HaveOccurred()) + + hypervisor := &kvmv1.Hypervisor{} + Expect(k8sClient.Get(ctx, hypervisorName, hypervisor)).To(Succeed()) + Expect(hypervisor.Status.Conditions).To(ContainElement( + SatisfyAll( + HaveField("Type", kvmv1.ConditionTypeTerminating), HaveField("Status", metav1.ConditionTrue), + HaveField("Reason", kvmv1.ConditionReasonTerminating), ), )) }) @@ -602,7 +635,7 @@ var _ = Describe("Hypervisor Controller", func() { SatisfyAll( HaveField("Type", kvmv1.ConditionTypeTerminating), HaveField("Status", metav1.ConditionTrue), - HaveField("Reason", terminatingReason), + HaveField("Reason", kvmv1.ConditionReasonTerminating), ), )) })