Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/api/internal/handlers/sandbox_kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ func (a *APIStore) DeleteSandboxesSandboxID(
killedOrRemoved := false

err = a.orchestrator.RemoveSandbox(ctx, teamID, sandboxID, sandbox.RemoveOpts{
Action: sandbox.StateActionKill,
Reason: sandbox.KillReasonRequest,
Action: sandbox.StateActionKill,
Reason: sandbox.KillReasonRequest,
RequesterIP: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
})
switch {
case err == nil:
Expand Down
1 change: 1 addition & 0 deletions packages/api/internal/orchestrator/create_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ func (o *Orchestrator) CreateSandbox(
sandbox.StateActionKill,
sandbox.KillReasonUnknown,
false, // kill: no snapshot
"", "",
)
if killErr != nil {
logger.L().Error(ctx, "Error removing sandbox",
Expand Down
18 changes: 14 additions & 4 deletions packages/api/internal/orchestrator/delete_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (o *Orchestrator) RemoveSandbox(ctx context.Context, teamID uuid.UUID, sand
defer func() { go o.analyticsRemove(context.WithoutCancel(ctx), sbx, opts.Action) }()
// Once we start the removal process, we want to make sure it gets removed from the store
defer o.sandboxStore.Remove(context.WithoutCancel(ctx), teamID, sandboxID)
err = o.removeSandboxFromNode(ctx, sbx, opts.Action, opts.Reason, opts.FilesystemOnly)
err = o.removeSandboxFromNode(ctx, sbx, opts.Action, opts.Reason, opts.FilesystemOnly, opts.RequesterIP, opts.UserAgent)
if err != nil {
fields := []zap.Field{
zap.String("state_action", opts.Action.Name),
Expand All @@ -128,6 +128,8 @@ func (o *Orchestrator) removeSandboxFromNode(
stateAction sandbox.StateAction,
reason sandbox.KillReason,
filesystemOnly bool,
requesterIP string,
userAgent string,
) error {
ctx, span := tracer.Start(ctx, "remove-sandbox-from-node")
defer span.End()
Expand Down Expand Up @@ -173,7 +175,7 @@ func (o *Orchestrator) removeSandboxFromNode(
err := o.pauseSandbox(ctx, node, sbx, filesystemOnly)
if err != nil {
if dberrors.IsForeignKeyViolation(err) {
killErr := o.killSandboxOnNode(ctx, node, sbx, sandbox.KillReasonBaseTemplateMissing)
killErr := o.killSandboxOnNode(ctx, node, sbx, sandbox.KillReasonBaseTemplateMissing, "", "")
logger.L().Error(ctx, "Pause failed due to missing base template, killed sandbox as fallback",
logger.WithSandboxID(sbx.SandboxID),
zap.String("base_template_id", sbx.BaseTemplateID),
Expand All @@ -190,7 +192,7 @@ func (o *Orchestrator) removeSandboxFromNode(

return nil
case sandbox.StateActionKill:
return o.killSandboxOnNode(ctx, node, sbx, reason)
return o.killSandboxOnNode(ctx, node, sbx, reason, requesterIP, userAgent)
}

return nil
Expand All @@ -208,7 +210,7 @@ func (o *Orchestrator) killOrphanSandbox(ctx context.Context, sbx sandbox.Sandbo
return
}

err := o.killSandboxOnNode(ctx, node, sbx, sandbox.KillReasonOrphaned)
err := o.killSandboxOnNode(ctx, node, sbx, sandbox.KillReasonOrphaned, "", "")
if err != nil {
logger.L().Error(ctx, "Failed to kill orphan sandbox on node",
zap.Error(err),
Expand All @@ -224,12 +226,20 @@ func (o *Orchestrator) killSandboxOnNode(
node *nodemanager.Node,
sbx sandbox.Sandbox,
reason sandbox.KillReason,
requesterIP string,
userAgent string,
) error {
killReason := reason.String()
req := &orchestrator.SandboxDeleteRequest{
SandboxId: sbx.SandboxID,
KillReason: &killReason,
}
if requesterIP != "" {
req.RequesterIp = &requesterIP
}
if userAgent != "" {
req.RequesterUserAgent = &userAgent
}

client, ctx := node.GetSandboxDeleteCtx(ctx, sbx.SandboxID, sbx.ExecutionID)
_, err := client.Sandbox.Delete(ctx, req)
Expand Down
6 changes: 6 additions & 0 deletions packages/api/internal/sandbox/sandboxtypes/states.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ type RemoveOpts struct {
// snapshot); resuming it cold-boots from disk. Ignored unless Action is a
// pause. Default false = full memory snapshot.
FilesystemOnly bool

// RequesterIP and UserAgent are populated for explicit API kill requests
// (KillReasonRequest) so that sandbox.lifecycle.killed events can record
// which client initiated the deletion.
RequesterIP string
UserAgent string
}

var AllowedTransitions = map[State]map[State]bool{
Expand Down
2 changes: 2 additions & 0 deletions packages/orchestrator/orchestrator.proto
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ message SandboxUpdateRequest {
message SandboxDeleteRequest {
string sandbox_id = 1;
optional string kill_reason = 2;
optional string requester_ip = 3;
optional string requester_user_agent = 4;
}

message SandboxPauseRequest {
Expand Down
6 changes: 6 additions & 0 deletions packages/orchestrator/pkg/server/sandboxes.go
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,12 @@ func (s *Server) Delete(ctxConn context.Context, in *orchestrator.SandboxDeleteR
teamID, buildId, eventsTTLDays, eventData := s.prepareSandboxEventData(ctx, sbx)
eventData[executionEventDataKey] = s.getSandboxExecutionData(sbx)
addKillReason(eventData, killReason)
if ip := in.GetRequesterIp(); ip != "" {
eventData["requester_ip"] = ip
}
if ua := in.GetRequesterUserAgent(); ua != "" {
eventData["requester_user_agent"] = ua
}
recordSandboxKill(ctx, s.sandboxKilledCounter, killReason)

eventType := events.SandboxKilledEventPair
Expand Down
Loading