@@ -68,7 +68,9 @@ var _ = Describe("NodeMaintenanceReconciler", func() {
6868 },
6969 }
7070 Expect (k8sClient .Create (ctx , node )).To (Succeed ())
71- DeferCleanup (k8sClient .Delete , node )
71+ DeferCleanup (func (ctx SpecContext ) error {
72+ return client .IgnoreNotFound (k8sClient .Delete (ctx , node ))
73+ })
7274
7375 originalNode := node .DeepCopy ()
7476 node .Spec .ProviderID = fmt .Sprintf ("metal://%s/%s" , serverClaim .Namespace , serverClaim .Name )
@@ -100,6 +102,9 @@ var _ = Describe("NodeMaintenanceReconciler", func() {
100102 Expect (maintenanceCR .Spec .Priority ).To (Equal (int32 (100 )))
101103 Expect (maintenanceCR .Spec .ServerRef ).NotTo (BeNil ())
102104 Expect (maintenanceCR .Spec .ServerRef .Name ).To (Equal (serverClaim .Spec .ServerRef .Name ))
105+
106+ By ("Verifying the finalizer is added to the Node" )
107+ Eventually (Object (node )).Should (HaveField ("Finalizers" , ContainElement (nodeMaintenanceFinalizer )))
103108 })
104109
105110 It ("should do nothing if ServerMaintenance CR already exists (idempotency)" , func (ctx SpecContext ) {
@@ -110,6 +115,7 @@ var _ = Describe("NodeMaintenanceReconciler", func() {
110115 Labels : map [string ]string {
111116 "test-marker" : "do-not-overwrite" ,
112117 },
118+ Finalizers : []string {nodeMaintenanceFinalizer },
113119 },
114120 Spec : metalv1alpha1.ServerMaintenanceSpec {
115121 Policy : metalv1alpha1 .ServerMaintenancePolicyOwnerApproval ,
@@ -136,6 +142,9 @@ var _ = Describe("NodeMaintenanceReconciler", func() {
136142 g .Expect (err ).NotTo (HaveOccurred ())
137143 g .Expect (checkCR .Labels ).To (HaveKeyWithValue ("test-marker" , "do-not-overwrite" ))
138144 }).Should (Succeed ())
145+
146+ By ("Verifying the finalizer is present in the Node" )
147+ Consistently (Object (node )).Should (HaveField ("Finalizers" , ContainElement (nodeMaintenanceFinalizer )))
139148 })
140149
141150 It ("should delete the ServerMaintenance CR when the maintenance-requested label is removed" , func (ctx SpecContext ) {
@@ -166,6 +175,9 @@ var _ = Describe("NodeMaintenanceReconciler", func() {
166175 g .Expect (err ).To (HaveOccurred ())
167176 g .Expect (apierrors .IsNotFound (err )).To (BeTrue ())
168177 }).Should (Succeed ())
178+
179+ By ("Verifying the finalizer is removed from the Node" )
180+ Eventually (Object (node )).ShouldNot (HaveField ("Finalizers" , ContainElement (nodeMaintenanceFinalizer )))
169181 })
170182
171183 It ("should do nothing if the label is absent and CR does not exist (idempotency)" , func (ctx SpecContext ) {
@@ -187,6 +199,47 @@ var _ = Describe("NodeMaintenanceReconciler", func() {
187199 g .Expect (err ).To (HaveOccurred ())
188200 g .Expect (apierrors .IsNotFound (err )).To (BeTrue ())
189201 }).Should (Succeed ())
202+
203+ Consistently (Object (node )).ShouldNot (HaveField ("Finalizers" , ContainElement (nodeMaintenanceFinalizer )))
204+ })
205+
206+ It ("should handle Node deletion by cleaning up the CR and removing the finalizer" , func (ctx SpecContext ) {
207+ By ("Triggering maintenance to create CR and add finalizer" )
208+ originalNode := node .DeepCopy ()
209+ if node .Labels == nil {
210+ node .Labels = make (map [string ]string )
211+ }
212+ node .Labels [metalv1alpha1 .ServerMaintenanceRequestedLabelKey ] = TrueStr
213+ Expect (k8sClient .Patch (ctx , node , client .MergeFrom (originalNode ))).To (Succeed ())
214+
215+ maintenanceKey := client.ObjectKey {
216+ Namespace : serverClaim .Namespace ,
217+ Name : serverClaim .Name ,
218+ }
219+ maintenanceCR := & metalv1alpha1.ServerMaintenance {}
220+
221+ Eventually (func (g Gomega ) {
222+ err := k8sClient .Get (ctx , maintenanceKey , maintenanceCR )
223+ g .Expect (err ).NotTo (HaveOccurred ())
224+ }).Should (Succeed ())
225+ Eventually (Object (node )).Should (HaveField ("Finalizers" , ContainElement (nodeMaintenanceFinalizer )))
226+
227+ By ("Deleting the Node" )
228+ Expect (k8sClient .Delete (ctx , node )).To (Succeed ())
229+
230+ By ("Verifying the ServerMaintenance CR is deleted first" )
231+ Eventually (func (g Gomega ) {
232+ err := k8sClient .Get (ctx , maintenanceKey , maintenanceCR )
233+ g .Expect (err ).To (HaveOccurred ())
234+ g .Expect (apierrors .IsNotFound (err )).To (BeTrue ())
235+ }).Should (Succeed ())
236+
237+ By ("Verifying the Node is completely deleted (finalizer was removed)" )
238+ Eventually (func (g Gomega ) {
239+ err := k8sClient .Get (ctx , client .ObjectKeyFromObject (node ), & corev1.Node {})
240+ g .Expect (err ).To (HaveOccurred ())
241+ g .Expect (apierrors .IsNotFound (err )).To (BeTrue ())
242+ }).Should (Succeed ())
190243 })
191244 })
192245
@@ -272,5 +325,8 @@ var _ = Describe("parseProviderID", func() {
272325 Entry ("missing provider before scheme" , "://default/node-1" , types.NamespacedName {}, true ),
273326 Entry ("missing namespace or name (no slash)" , "metal://node-1" , types.NamespacedName {}, true ),
274327 Entry ("too many slashes" , "metal://default/node-1/extra" , types.NamespacedName {}, true ),
328+ Entry ("empty namespace" , "metal:///name" , types.NamespacedName {}, true ),
329+ Entry ("empty name" , "metal://namespace/" , types.NamespacedName {}, true ),
330+ Entry ("empty name and namespace" , "metal:///" , types.NamespacedName {}, true ),
275331 )
276332})
0 commit comments