Skip to content

Commit 6e7735c

Browse files
committed
fix(workloadmanager): include sandbox name/namespace in store delete error log
Signed-off-by: Abhinav-kodes <183825080+Abhinav-kodes@users.noreply.github.com> Signed-off-by: Abhinav Singh <abhinavsingh717073@gmail.com>
1 parent 3476879 commit 6e7735c

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

pkg/workloadmanager/handlers.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ import (
3939
// errSandboxCreationTimeout is returned when the internal sandbox-ready wait exceeds the 2-minute deadline.
4040
var errSandboxCreationTimeout = errors.New("sandbox creation timed out")
4141

42+
// storeCleanupTimeout is the maximum duration allowed to clean up a store placeholder.
43+
const storeCleanupTimeout = 30 * time.Second
44+
4245
// isContextError reports whether err is a context cancellation or deadline error.
4346
func isContextError(err error) bool {
4447
return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)
@@ -284,7 +287,7 @@ func (s *Server) createSandbox(ctx context.Context, dynamicClient dynamic.Interf
284287
// placeholder when creation fails. It runs in a fresh context so that a
285288
// canceled request context does not prevent cleanup.
286289
func (s *Server) rollbackSandboxCreation(dynamicClient dynamic.Interface, sandbox *sandboxv1alpha1.Sandbox, sandboxClaim *extensionsv1alpha1.SandboxClaim, sessionID string) {
287-
ctxTimeout, cancel := context.WithTimeout(context.Background(), 30*time.Second)
290+
ctxTimeout, cancel := context.WithTimeout(context.Background(), storeCleanupTimeout)
288291
defer cancel()
289292
if sandboxClaim != nil {
290293
if err := deleteSandboxClaim(ctxTimeout, dynamicClient, sandboxClaim.Namespace, sandboxClaim.Name); err != nil {
@@ -357,13 +360,13 @@ func (s *Server) handleDeleteSandbox(c *gin.Context) {
357360

358361
// Use a detached context for the store delete so a client disconnect
359362
// after K8s deletion doesn't orphan the store entry.
360-
deleteCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
363+
deleteCtx, cancel := context.WithTimeout(context.Background(), storeCleanupTimeout)
361364
defer cancel()
362365

363366
// Delete sandbox from store
364367
err = s.storeClient.DeleteSandboxBySessionID(deleteCtx, sessionID)
365368
if err != nil {
366-
klog.Errorf("delete sandbox from store by sessionID %s failed: %v", sessionID, err)
369+
klog.Errorf("delete %s %s/%s from store by sessionID %s failed: %v", sandbox.Kind, sandbox.SandboxNamespace, sandbox.Name, sessionID, err)
367370
respondError(c, http.StatusInternalServerError, "internal server error")
368371
return
369372
}

pkg/workloadmanager/handlers_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,3 +475,59 @@ func TestHandleSandboxCreate(t *testing.T) {
475475
})
476476
}
477477
}
478+
479+
/*
480+
This test verifies that the deleteSandbox handler correctly handles scenarios where the client disconnects (Context Cancellation) before the deletion operation completes.
481+
482+
Key Points:
483+
484+
The handler creates a new context for the K8s deletion operation using context.WithTimeout(ctx, deletionTimeout).
485+
This derived context remains valid even if the parent context (c.Request.Context()) is canceled.
486+
The test simulates a client disconnect by canceling the request context immediately after calling the deleteSandbox function.
487+
It verifies that the store deletion (the final cleanup step) still occurs by checking that the store's DeleteSandboxBySessionID method was called with a valid, non-canceled context.
488+
*/
489+
func TestHandleDeleteSandbox_DetachedContext(t *testing.T) {
490+
gin.SetMode(gin.TestMode)
491+
fakeServer := newFakeServer()
492+
493+
fakeStoreInst := &fakeStore{}
494+
fakeServer.storeClient = fakeStoreInst
495+
496+
patches := gomonkey.NewPatches()
497+
defer patches.Reset()
498+
499+
patches.ApplyMethod(reflect.TypeOf((*fakeStore)(nil)), "GetSandboxBySessionID", func(_ *fakeStore, _ context.Context, _ string) (*types.SandboxInfo, error) {
500+
return &types.SandboxInfo{
501+
Kind: types.AgentRuntimeKind,
502+
SandboxNamespace: "ns-1",
503+
Name: "sandbox-1",
504+
SessionID: "sess-1",
505+
}, nil
506+
})
507+
508+
w := httptest.NewRecorder()
509+
c, _ := gin.CreateTestContext(w)
510+
req := httptest.NewRequest(http.MethodDelete, "/sandboxes/sess-1", nil)
511+
512+
reqCtx, cancelReq := context.WithCancel(context.Background())
513+
req = req.WithContext(reqCtx)
514+
c.Request = req
515+
c.Params = gin.Params{{Key: "sessionId", Value: "sess-1"}}
516+
517+
patches.ApplyFunc(deleteSandbox, func(_ context.Context, _ dynamic.Interface, _, _ string) error {
518+
cancelReq()
519+
return nil
520+
})
521+
522+
storeDeleteCalled := false
523+
patches.ApplyMethod(reflect.TypeOf((*fakeStore)(nil)), "DeleteSandboxBySessionID", func(_ *fakeStore, ctx context.Context, _ string) error {
524+
require.NoError(t, ctx.Err(), "Store deletion context MUST NOT be canceled despite client disconnect")
525+
storeDeleteCalled = true
526+
return nil
527+
})
528+
529+
fakeServer.handleDeleteSandbox(c)
530+
531+
require.True(t, storeDeleteCalled, "DeleteSandboxBySessionID should be called even if the request context is canceled")
532+
require.Equal(t, http.StatusOK, w.Code)
533+
}

0 commit comments

Comments
 (0)