@@ -21,6 +21,7 @@ import (
2121 "testing"
2222 "time"
2323
24+ "github.com/NVIDIA/aicr/pkg/defaults"
2425 aicrErrors "github.com/NVIDIA/aicr/pkg/errors"
2526 "github.com/NVIDIA/aicr/validators"
2627 coordinationv1 "k8s.io/api/coordination/v1"
@@ -151,7 +152,7 @@ func testHeldLease(namespace string) *coordinationv1.Lease {
151152func TestCleanupNCCLResources_ToleratesMissing (t * testing.T ) {
152153 const ns = "aicr-nccl-perf-deadbeef"
153154 fakeClient := fake .NewClientset ()
154- if err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID ); err != nil {
155+ if err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID , defaults . InferenceNamespaceTerminationWait ); err != nil {
155156 t .Fatalf ("cleanup of a namespace that was never created should not error, got: %v" , err )
156157 }
157158}
@@ -165,7 +166,7 @@ func TestCleanupNCCLResources_DeletesNamespace(t *testing.T) {
165166 const ns = "aicr-nccl-perf-deadbeef"
166167 fakeClient := fake .NewClientset (& v1.Namespace {ObjectMeta : metav1.ObjectMeta {Name : ns , UID : testNamespaceUID }})
167168
168- if err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID ); err != nil {
169+ if err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID , defaults . InferenceNamespaceTerminationWait ); err != nil {
169170 t .Fatalf ("cleanup should not error, got: %v" , err )
170171 }
171172
@@ -185,7 +186,7 @@ func TestCleanupNCCLResources_ReturnsErrorOnDeleteFailure(t *testing.T) {
185186 return true , nil , apierrors .NewServiceUnavailable ("apiserver is down" )
186187 })
187188
188- err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID )
189+ err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID , defaults . InferenceNamespaceTerminationWait )
189190 if err == nil {
190191 t .Fatal ("expected an error from a non-NotFound namespace delete failure, got nil" )
191192 }
@@ -203,7 +204,7 @@ func TestCleanupNCCLResources_RejectsEmptyUID(t *testing.T) {
203204 const ns = "aicr-nccl-perf-deadbeef"
204205 fakeClient := fake .NewClientset (& v1.Namespace {ObjectMeta : metav1.ObjectMeta {Name : ns , UID : testNamespaceUID }})
205206
206- err := cleanupNCCLResources (fakeClient , ns , "" )
207+ err := cleanupNCCLResources (fakeClient , ns , "" , defaults . InferenceNamespaceTerminationWait )
207208 if err == nil {
208209 t .Fatal ("expected an error for an empty owning UID, got nil" )
209210 }
@@ -236,7 +237,7 @@ func TestCleanupNCCLResources_UIDMismatchPreventsDelete(t *testing.T) {
236237 stderrors .New ("uid in precondition does not match uid in record" ))
237238 })
238239
239- if err := cleanupNCCLResources (fakeClient , ns , "wrong-uid" ); err != nil {
240+ if err := cleanupNCCLResources (fakeClient , ns , "wrong-uid" , defaults . InferenceNamespaceTerminationWait ); err != nil {
240241 t .Fatalf ("expected a UID mismatch to be treated as already-replaced, got err=%v" , err )
241242 }
242243 if _ , getErr := fakeClient .CoreV1 ().Namespaces ().Get (context .Background (), ns , metav1.GetOptions {}); getErr != nil {
@@ -289,7 +290,7 @@ func TestCleanupNCCLResources_WaitsForFinalizerHeldNamespace(t *testing.T) {
289290 _ = fakeClient .CoreV1 ().Namespaces ().Delete (context .Background (), ns , metav1.DeleteOptions {})
290291 }()
291292
292- if err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID ); err != nil {
293+ if err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID , defaults . InferenceNamespaceTerminationWait ); err != nil {
293294 t .Fatalf ("cleanup should succeed once the finalizer clears, got: %v" , err )
294295 }
295296 elapsed := time .Since (start )
@@ -306,12 +307,9 @@ func TestCleanupNCCLResources_WaitsForFinalizerHeldNamespace(t *testing.T) {
306307 }
307308}
308309
309- // TestWaitForNamespaceGone_TimesOutWhenNeverDeleted guards the bounded-wait
310- // contract of waitForNamespaceGone itself. If finalizers never clear within
311- // the deadline, it must return ErrCodeTimeout rather than hang indefinitely
312- // (cleanupNCCLResources itself only logs this and returns nil). Calls it
313- // directly with a short local context to avoid the real 5-minute
314- // production bound.
310+ // TestWaitForNamespaceGone_TimesOutWhenNeverDeleted verifies that
311+ // waitForNamespaceGone returns ErrCodeTimeout, not a hang, when a
312+ // namespace's finalizers never clear before the context deadline.
315313func TestWaitForNamespaceGone_TimesOutWhenNeverDeleted (t * testing.T ) {
316314 const ns = "aicr-nccl-perf-deadbeef"
317315 now := metav1 .Now ()
@@ -321,6 +319,7 @@ func TestWaitForNamespaceGone_TimesOutWhenNeverDeleted(t *testing.T) {
321319 DeletionTimestamp : & now ,
322320 }})
323321
322+ // Short deadline to avoid the real 5-minute production bound.
324323 ctx , cancel := context .WithTimeout (context .Background (), 100 * time .Millisecond )
325324 defer cancel ()
326325
@@ -332,3 +331,35 @@ func TestWaitForNamespaceGone_TimesOutWhenNeverDeleted(t *testing.T) {
332331 t .Errorf ("got %v, want an ErrCodeTimeout-wrapped wait failure" , err )
333332 }
334333}
334+
335+ // TestCleanupNCCLResources_ReturnsErrorOnTerminationTimeout verifies that
336+ // cleanupNCCLResources fails, rather than reporting a false "Deleted",
337+ // when a namespace is still held by a finalizer after the wait bound
338+ // expires.
339+ func TestCleanupNCCLResources_ReturnsErrorOnTerminationTimeout (t * testing.T ) {
340+ const ns = "aicr-nccl-perf-deadbeef"
341+ now := metav1 .Now ()
342+ fakeClient := fake .NewClientset (& v1.Namespace {ObjectMeta : metav1.ObjectMeta {
343+ Name : ns , UID : testNamespaceUID ,
344+ }})
345+ fakeClient .PrependReactor ("delete" , "namespaces" , func (k8stesting.Action ) (bool , runtime.Object , error ) {
346+ // Accept the delete but never actually remove the object, simulating
347+ // a finalizer that never clears within the wait bound below.
348+ held := & v1.Namespace {ObjectMeta : metav1.ObjectMeta {
349+ Name : ns , UID : testNamespaceUID , Finalizers : []string {"kubernetes" }, DeletionTimestamp : & now ,
350+ }}
351+ nsGVR := v1 .SchemeGroupVersion .WithResource ("namespaces" )
352+ if err := fakeClient .Tracker ().Update (nsGVR , held , "" ); err != nil {
353+ return true , nil , err
354+ }
355+ return true , nil , nil
356+ })
357+
358+ err := cleanupNCCLResources (fakeClient , ns , testNamespaceUID , 100 * time .Millisecond )
359+ if err == nil {
360+ t .Fatal ("expected cleanup to fail when the namespace never finishes terminating, got nil" )
361+ }
362+ if ! stderrors .Is (err , aicrErrors .New (aicrErrors .ErrCodeTimeout , "" )) {
363+ t .Errorf ("got %v, want an ErrCodeTimeout-wrapped cleanup failure" , err )
364+ }
365+ }
0 commit comments