diff --git a/packages/api/internal/api/api.gen.go b/packages/api/internal/api/api.gen.go index d56e9a00ef..ef14190759 100644 --- a/packages/api/internal/api/api.gen.go +++ b/packages/api/internal/api/api.gen.go @@ -59,6 +59,9 @@ type ServerInterface interface { // (GET /sandboxes/{sandboxID}) GetSandboxesSandboxID(c *gin.Context, sandboxID SandboxID) + // (POST /sandboxes/{sandboxID}/connect) + PostSandboxesSandboxIDConnect(c *gin.Context, sandboxID SandboxID) + // (GET /sandboxes/{sandboxID}/logs) GetSandboxesSandboxIDLogs(c *gin.Context, sandboxID SandboxID, params GetSandboxesSandboxIDLogsParams) @@ -501,6 +504,36 @@ func (siw *ServerInterfaceWrapper) GetSandboxesSandboxID(c *gin.Context) { siw.Handler.GetSandboxesSandboxID(c, sandboxID) } +// PostSandboxesSandboxIDConnect operation middleware +func (siw *ServerInterfaceWrapper) PostSandboxesSandboxIDConnect(c *gin.Context) { + + var err error + + // ------------- Path parameter "sandboxID" ------------- + var sandboxID SandboxID + + err = runtime.BindStyledParameterWithOptions("simple", "sandboxID", c.Param("sandboxID"), &sandboxID, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter sandboxID: %w", err), http.StatusBadRequest) + return + } + + c.Set(ApiKeyAuthScopes, []string{}) + + c.Set(Supabase1TokenAuthScopes, []string{}) + + c.Set(Supabase2TeamAuthScopes, []string{}) + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.PostSandboxesSandboxIDConnect(c, sandboxID) +} + // GetSandboxesSandboxIDLogs operation middleware func (siw *ServerInterfaceWrapper) GetSandboxesSandboxIDLogs(c *gin.Context) { @@ -1272,6 +1305,7 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.GET(options.BaseURL+"/sandboxes/metrics", wrapper.GetSandboxesMetrics) router.DELETE(options.BaseURL+"/sandboxes/:sandboxID", wrapper.DeleteSandboxesSandboxID) router.GET(options.BaseURL+"/sandboxes/:sandboxID", wrapper.GetSandboxesSandboxID) + router.POST(options.BaseURL+"/sandboxes/:sandboxID/connect", wrapper.PostSandboxesSandboxIDConnect) router.GET(options.BaseURL+"/sandboxes/:sandboxID/logs", wrapper.GetSandboxesSandboxIDLogs) router.GET(options.BaseURL+"/sandboxes/:sandboxID/metrics", wrapper.GetSandboxesSandboxIDMetrics) router.POST(options.BaseURL+"/sandboxes/:sandboxID/pause", wrapper.PostSandboxesSandboxIDPause) diff --git a/packages/api/internal/api/spec.gen.go b/packages/api/internal/api/spec.gen.go index 6b99622196..a69c86bd7b 100644 --- a/packages/api/internal/api/spec.gen.go +++ b/packages/api/internal/api/spec.gen.go @@ -18,111 +18,113 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "S92/G31U3Qe2Ldvt2e1/AwAA///zg27DLbgAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/packages/api/internal/api/types.gen.go b/packages/api/internal/api/types.gen.go index 7b48695a4a..a458178bed 100644 --- a/packages/api/internal/api/types.gen.go +++ b/packages/api/internal/api/types.gen.go @@ -119,6 +119,12 @@ type BuildStatusReason struct { // CPUCount CPU cores for the sandbox type CPUCount = int32 +// ConnectSandbox defines model for ConnectSandbox. +type ConnectSandbox struct { + // Timeout Timeout in seconds from the current time after which the sandbox should expire + Timeout int32 `json:"timeout"` +} + // CreatedAccessToken defines model for CreatedAccessToken. type CreatedAccessToken struct { // CreatedAt Timestamp of access token creation @@ -968,6 +974,9 @@ type PostNodesNodeIDJSONRequestBody = NodeStatusChange // PostSandboxesJSONRequestBody defines body for PostSandboxes for application/json ContentType. type PostSandboxesJSONRequestBody = NewSandbox +// PostSandboxesSandboxIDConnectJSONRequestBody defines body for PostSandboxesSandboxIDConnect for application/json ContentType. +type PostSandboxesSandboxIDConnectJSONRequestBody = ConnectSandbox + // PostSandboxesSandboxIDRefreshesJSONRequestBody defines body for PostSandboxesSandboxIDRefreshes for application/json ContentType. type PostSandboxesSandboxIDRefreshesJSONRequestBody PostSandboxesSandboxIDRefreshesJSONBody diff --git a/packages/api/internal/handlers/sandbox_connect.go b/packages/api/internal/handlers/sandbox_connect.go new file mode 100644 index 0000000000..78971a891b --- /dev/null +++ b/packages/api/internal/handlers/sandbox_connect.go @@ -0,0 +1,167 @@ +package handlers + +import ( + "database/sql" + "errors" + "fmt" + "net/http" + "time" + + "github.com/gin-gonic/gin" + "go.opentelemetry.io/otel/trace" + "go.uber.org/zap" + + "github.com/e2b-dev/infra/packages/api/internal/api" + "github.com/e2b-dev/infra/packages/api/internal/auth" + "github.com/e2b-dev/infra/packages/api/internal/db/types" + "github.com/e2b-dev/infra/packages/api/internal/sandbox" + "github.com/e2b-dev/infra/packages/api/internal/utils" + "github.com/e2b-dev/infra/packages/db/queries" + "github.com/e2b-dev/infra/packages/shared/pkg/logger" + sbxlogger "github.com/e2b-dev/infra/packages/shared/pkg/logger/sandbox" + "github.com/e2b-dev/infra/packages/shared/pkg/telemetry" +) + +func (a *APIStore) PostSandboxesSandboxIDConnect(c *gin.Context, sandboxID api.SandboxID) { + ctx := c.Request.Context() + + // Get team from context, use TeamContextKey + teamInfo := c.Value(auth.TeamContextKey).(*types.Team) + + span := trace.SpanFromContext(ctx) + traceID := span.SpanContext().TraceID().String() + c.Set("traceID", traceID) + + telemetry.ReportEvent(ctx, "Parsed body") + + body, err := utils.ParseBody[api.PostSandboxesSandboxIDConnectJSONRequestBody](ctx, c) + if err != nil { + a.sendAPIStoreError(c, http.StatusBadRequest, fmt.Sprintf("Error when parsing request: %s", err)) + + telemetry.ReportCriticalError(ctx, "error when parsing request", err) + + return + } + + timeout := time.Duration(body.Timeout) * time.Second + if timeout > time.Duration(teamInfo.Limits.MaxLengthHours)*time.Hour { + a.sendAPIStoreError(c, http.StatusBadRequest, fmt.Sprintf("Timeout cannot be greater than %d hours", teamInfo.Limits.MaxLengthHours)) + + return + } + + sandboxID = utils.ShortID(sandboxID) + sandboxData, err := a.orchestrator.GetSandbox(sandboxID) + if err == nil { + switch sandboxData.State { + case sandbox.StatePausing: + zap.L().Debug("Waiting for sandbox to pause", logger.WithSandboxID(sandboxID)) + err = a.orchestrator.WaitForStateChange(ctx, sandboxID) + if err != nil { + a.sendAPIStoreError(c, http.StatusInternalServerError, "Error waiting for sandbox to pause") + + return + } + case sandbox.StateKilling: + a.sendAPIStoreError(c, http.StatusNotFound, "Sandbox can't be resumed, no snapshot found") + + return + case sandbox.StateRunning: + zap.L().Debug("Sandbox is already running", + logger.WithSandboxID(sandboxID), + zap.Time("end_time", sandboxData.EndTime), + zap.Time("start_time", sandboxData.StartTime), + zap.String("node_id", sandboxData.NodeID), + ) + + apiErr := a.orchestrator.KeepAliveFor(ctx, sandboxID, timeout, false) + if apiErr != nil { + zap.L().Error("Error when resuming sandbox", zap.Error(apiErr.Err)) + a.sendAPIStoreError(c, apiErr.Code, apiErr.ClientMsg) + + return + } + + c.JSON(http.StatusOK, sandboxData.ToAPISandbox()) + + return + default: + zap.L().Error("Sandbox is in an unknown state", logger.WithSandboxID(sandboxID), zap.String("state", string(sandboxData.State))) + a.sendAPIStoreError(c, http.StatusInternalServerError, "Sandbox is in an unknown state") + + return + } + } + + lastSnapshot, err := a.sqlcDB.GetLastSnapshot(ctx, queries.GetLastSnapshotParams{SandboxID: sandboxID, TeamID: teamInfo.Team.ID}) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + zap.L().Debug("Snapshot not found", logger.WithSandboxID(sandboxID)) + a.sendAPIStoreError(c, http.StatusNotFound, "Sandbox can't be resumed, no snapshot found") + + return + } + + zap.L().Error("Error getting last snapshot", logger.WithSandboxID(sandboxID), zap.Error(err)) + a.sendAPIStoreError(c, http.StatusInternalServerError, "Error when getting snapshot") + + return + } + + autoPause := lastSnapshot.Snapshot.AutoPause + snap := lastSnapshot.Snapshot + build := lastSnapshot.EnvBuild + + nodeID := &snap.OriginNodeID + + alias := "" + if len(lastSnapshot.Aliases) > 0 { + alias = lastSnapshot.Aliases[0] + } + + sbxlogger.E(&sbxlogger.SandboxMetadata{ + SandboxID: sandboxID, + TemplateID: build.EnvID, + TeamID: teamInfo.Team.ID.String(), + }).Debug("Started resuming sandbox") + + var envdAccessToken *string = nil + if snap.EnvSecure { + accessToken, tokenErr := a.getEnvdAccessToken(build.EnvdVersion, sandboxID) + if tokenErr != nil { + zap.L().Error("Secure envd access token error", zap.Error(tokenErr.Err), logger.WithTemplateID(build.EnvID), logger.WithBuildID(build.ID.String()), logger.WithSandboxID(sandboxID)) + a.sendAPIStoreError(c, tokenErr.Code, tokenErr.ClientMsg) + + return + } + + envdAccessToken = &accessToken + } + + sbx, createErr := a.startSandbox( + ctx, + snap.SandboxID, + timeout, + nil, + snap.Metadata, + alias, + teamInfo, + build, + &c.Request.Header, + true, + nodeID, + snap.BaseEnvID, + autoPause, + envdAccessToken, + snap.AllowInternetAccess, + nil, + ) + if createErr != nil { + zap.L().Error("Failed to resume sandbox", zap.Error(createErr.Err)) + a.sendAPIStoreError(c, createErr.Code, createErr.ClientMsg) + + return + } + + c.JSON(http.StatusCreated, &sbx) +} diff --git a/spec/openapi.yml b/spec/openapi.yml index e57d3e448d..ba8b077bed 100644 --- a/spec/openapi.yml +++ b/spec/openapi.yml @@ -478,6 +478,17 @@ components: deprecated: true description: Automatically pauses the sandbox after the timeout + ConnectSandbox: + type: object + required: + - timeout + properties: + timeout: + description: Timeout in seconds from the current time after which the sandbox should expire + type: integer + format: int32 + minimum: 0 + TeamMetric: description: Team metric with timestamp required: @@ -1620,6 +1631,7 @@ paths: /sandboxes/{sandboxID}/resume: post: + deprecated: true description: Resume the sandbox tags: [sandboxes] security: @@ -1650,6 +1662,44 @@ paths: "500": $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/connect: + post: + description: Returns sandbox details. If the sandbox is paused, it will be resumed. TTL is only extended. + tags: [sandboxes] + security: + - ApiKeyAuth: [] + - Supabase1TokenAuth: [] + Supabase2TeamAuth: [] + parameters: + - $ref: "#/components/parameters/sandboxID" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ConnectSandbox" + responses: + "200": + description: The sandbox was already running + content: + application/json: + schema: + $ref: "#/components/schemas/Sandbox" + "201": + description: The sandbox was resumed successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Sandbox" + "400": + $ref: "#/components/responses/400" + "401": + $ref: "#/components/responses/401" + "404": + $ref: "#/components/responses/404" + "500": + $ref: "#/components/responses/500" + /sandboxes/{sandboxID}/timeout: post: description: Set the timeout for the sandbox. The sandbox will expire x seconds from the time of the request. Calling this method multiple times overwrites the TTL, each time using the current timestamp as the starting point to measure the timeout duration. diff --git a/tests/integration/internal/api/client.gen.go b/tests/integration/internal/api/client.gen.go index b2440d179b..82e0ba71fc 100644 --- a/tests/integration/internal/api/client.gen.go +++ b/tests/integration/internal/api/client.gen.go @@ -144,6 +144,11 @@ type ClientInterface interface { // GetSandboxesSandboxID request GetSandboxesSandboxID(ctx context.Context, sandboxID SandboxID, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostSandboxesSandboxIDConnectWithBody request with any body + PostSandboxesSandboxIDConnectWithBody(ctx context.Context, sandboxID SandboxID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostSandboxesSandboxIDConnect(ctx context.Context, sandboxID SandboxID, body PostSandboxesSandboxIDConnectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetSandboxesSandboxIDLogs request GetSandboxesSandboxIDLogs(ctx context.Context, sandboxID SandboxID, params *GetSandboxesSandboxIDLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -461,6 +466,30 @@ func (c *Client) GetSandboxesSandboxID(ctx context.Context, sandboxID SandboxID, return c.Client.Do(req) } +func (c *Client) PostSandboxesSandboxIDConnectWithBody(ctx context.Context, sandboxID SandboxID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostSandboxesSandboxIDConnectRequestWithBody(c.Server, sandboxID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostSandboxesSandboxIDConnect(ctx context.Context, sandboxID SandboxID, body PostSandboxesSandboxIDConnectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostSandboxesSandboxIDConnectRequest(c.Server, sandboxID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetSandboxesSandboxIDLogs(ctx context.Context, sandboxID SandboxID, params *GetSandboxesSandboxIDLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetSandboxesSandboxIDLogsRequest(c.Server, sandboxID, params) if err != nil { @@ -1378,6 +1407,53 @@ func NewGetSandboxesSandboxIDRequest(server string, sandboxID SandboxID) (*http. return req, nil } +// NewPostSandboxesSandboxIDConnectRequest calls the generic PostSandboxesSandboxIDConnect builder with application/json body +func NewPostSandboxesSandboxIDConnectRequest(server string, sandboxID SandboxID, body PostSandboxesSandboxIDConnectJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostSandboxesSandboxIDConnectRequestWithBody(server, sandboxID, "application/json", bodyReader) +} + +// NewPostSandboxesSandboxIDConnectRequestWithBody generates requests for PostSandboxesSandboxIDConnect with any type of body +func NewPostSandboxesSandboxIDConnectRequestWithBody(server string, sandboxID SandboxID, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "sandboxID", runtime.ParamLocationPath, sandboxID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/sandboxes/%s/connect", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewGetSandboxesSandboxIDLogsRequest generates requests for GetSandboxesSandboxIDLogs func NewGetSandboxesSandboxIDLogsRequest(server string, sandboxID SandboxID, params *GetSandboxesSandboxIDLogsParams) (*http.Request, error) { var err error @@ -2547,6 +2623,11 @@ type ClientWithResponsesInterface interface { // GetSandboxesSandboxIDWithResponse request GetSandboxesSandboxIDWithResponse(ctx context.Context, sandboxID SandboxID, reqEditors ...RequestEditorFn) (*GetSandboxesSandboxIDResponse, error) + // PostSandboxesSandboxIDConnectWithBodyWithResponse request with any body + PostSandboxesSandboxIDConnectWithBodyWithResponse(ctx context.Context, sandboxID SandboxID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSandboxesSandboxIDConnectResponse, error) + + PostSandboxesSandboxIDConnectWithResponse(ctx context.Context, sandboxID SandboxID, body PostSandboxesSandboxIDConnectJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSandboxesSandboxIDConnectResponse, error) + // GetSandboxesSandboxIDLogsWithResponse request GetSandboxesSandboxIDLogsWithResponse(ctx context.Context, sandboxID SandboxID, params *GetSandboxesSandboxIDLogsParams, reqEditors ...RequestEditorFn) (*GetSandboxesSandboxIDLogsResponse, error) @@ -2987,6 +3068,33 @@ func (r GetSandboxesSandboxIDResponse) StatusCode() int { return 0 } +type PostSandboxesSandboxIDConnectResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Sandbox + JSON201 *Sandbox + JSON400 *N400 + JSON401 *N401 + JSON404 *N404 + JSON500 *N500 +} + +// Status returns HTTPResponse.Status +func (r PostSandboxesSandboxIDConnectResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostSandboxesSandboxIDConnectResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetSandboxesSandboxIDLogsResponse struct { Body []byte HTTPResponse *http.Response @@ -3654,6 +3762,23 @@ func (c *ClientWithResponses) GetSandboxesSandboxIDWithResponse(ctx context.Cont return ParseGetSandboxesSandboxIDResponse(rsp) } +// PostSandboxesSandboxIDConnectWithBodyWithResponse request with arbitrary body returning *PostSandboxesSandboxIDConnectResponse +func (c *ClientWithResponses) PostSandboxesSandboxIDConnectWithBodyWithResponse(ctx context.Context, sandboxID SandboxID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostSandboxesSandboxIDConnectResponse, error) { + rsp, err := c.PostSandboxesSandboxIDConnectWithBody(ctx, sandboxID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostSandboxesSandboxIDConnectResponse(rsp) +} + +func (c *ClientWithResponses) PostSandboxesSandboxIDConnectWithResponse(ctx context.Context, sandboxID SandboxID, body PostSandboxesSandboxIDConnectJSONRequestBody, reqEditors ...RequestEditorFn) (*PostSandboxesSandboxIDConnectResponse, error) { + rsp, err := c.PostSandboxesSandboxIDConnect(ctx, sandboxID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostSandboxesSandboxIDConnectResponse(rsp) +} + // GetSandboxesSandboxIDLogsWithResponse request returning *GetSandboxesSandboxIDLogsResponse func (c *ClientWithResponses) GetSandboxesSandboxIDLogsWithResponse(ctx context.Context, sandboxID SandboxID, params *GetSandboxesSandboxIDLogsParams, reqEditors ...RequestEditorFn) (*GetSandboxesSandboxIDLogsResponse, error) { rsp, err := c.GetSandboxesSandboxIDLogs(ctx, sandboxID, params, reqEditors...) @@ -4519,6 +4644,67 @@ func ParseGetSandboxesSandboxIDResponse(rsp *http.Response) (*GetSandboxesSandbo return response, nil } +// ParsePostSandboxesSandboxIDConnectResponse parses an HTTP response from a PostSandboxesSandboxIDConnectWithResponse call +func ParsePostSandboxesSandboxIDConnectResponse(rsp *http.Response) (*PostSandboxesSandboxIDConnectResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostSandboxesSandboxIDConnectResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Sandbox + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Sandbox + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest N400 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest N401 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest N404 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest N500 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetSandboxesSandboxIDLogsResponse parses an HTTP response from a GetSandboxesSandboxIDLogsWithResponse call func ParseGetSandboxesSandboxIDLogsResponse(rsp *http.Response) (*GetSandboxesSandboxIDLogsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/tests/integration/internal/api/models.gen.go b/tests/integration/internal/api/models.gen.go index 7b48695a4a..a458178bed 100644 --- a/tests/integration/internal/api/models.gen.go +++ b/tests/integration/internal/api/models.gen.go @@ -119,6 +119,12 @@ type BuildStatusReason struct { // CPUCount CPU cores for the sandbox type CPUCount = int32 +// ConnectSandbox defines model for ConnectSandbox. +type ConnectSandbox struct { + // Timeout Timeout in seconds from the current time after which the sandbox should expire + Timeout int32 `json:"timeout"` +} + // CreatedAccessToken defines model for CreatedAccessToken. type CreatedAccessToken struct { // CreatedAt Timestamp of access token creation @@ -968,6 +974,9 @@ type PostNodesNodeIDJSONRequestBody = NodeStatusChange // PostSandboxesJSONRequestBody defines body for PostSandboxes for application/json ContentType. type PostSandboxesJSONRequestBody = NewSandbox +// PostSandboxesSandboxIDConnectJSONRequestBody defines body for PostSandboxesSandboxIDConnect for application/json ContentType. +type PostSandboxesSandboxIDConnectJSONRequestBody = ConnectSandbox + // PostSandboxesSandboxIDRefreshesJSONRequestBody defines body for PostSandboxesSandboxIDRefreshes for application/json ContentType. type PostSandboxesSandboxIDRefreshesJSONRequestBody PostSandboxesSandboxIDRefreshesJSONBody diff --git a/tests/integration/internal/tests/api/sandboxes/sandbox_connect_test.go b/tests/integration/internal/tests/api/sandboxes/sandbox_connect_test.go new file mode 100644 index 0000000000..8d031eb075 --- /dev/null +++ b/tests/integration/internal/tests/api/sandboxes/sandbox_connect_test.go @@ -0,0 +1,173 @@ +package sandboxes + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/e2b-dev/infra/tests/integration/internal/api" + "github.com/e2b-dev/infra/tests/integration/internal/setup" + "github.com/e2b-dev/infra/tests/integration/internal/utils" +) + +func TestSandboxConnect(t *testing.T) { + c := setup.GetAPIClient() + + t.Run("connect with paused sandbox", func(t *testing.T) { + // Create a sandbox with auto-pause disabled + sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithAutoPause(false)) + sbxId := sbx.SandboxID + pauseSandbox(t, c, sbxId) + + // Connect to the sandbox + sbxConnect, err := c.PostSandboxesSandboxIDConnectWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: 30, + }, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusCreated, sbxConnect.StatusCode()) + require.NotNil(t, sbxConnect.JSON201) + assert.Equal(t, sbxConnect.JSON201.SandboxID, sbxId) + + // Check if the sandbox is running + res, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode()) + require.NotNil(t, res.JSON200) + assert.Equal(t, api.Running, res.JSON200.State) + }) + + t.Run("connect to running sandbox", func(t *testing.T) { + // Create a sandbox with auto-pause disabled + sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithTimeout(100)) + sbxId := sbx.SandboxID + + res, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode()) + require.NotNil(t, res.JSON200) + assert.Equal(t, api.Running, res.JSON200.State) + + initialEndTime := res.JSON200.EndAt + + // Connect to the sandbox + sbxConnect, err := c.PostSandboxesSandboxIDConnectWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: 10, + }, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, sbxConnect.StatusCode()) + require.NotNil(t, sbxConnect.JSON200) + assert.Equal(t, sbxConnect.JSON200.SandboxID, sbxId) + + // Check if the sandbox is running and the timeout isn't changed + res, err = c.GetSandboxesSandboxIDWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode()) + require.NotNil(t, res.JSON200) + assert.Equal(t, api.Running, res.JSON200.State) + + assert.Equal(t, initialEndTime, res.JSON200.EndAt, "the timeout shouldn't be changed") + }) + + t.Run("connect to running sandbox shorter timeout", func(t *testing.T) { + // Create a sandbox with auto-pause disabled + sbx := utils.SetupSandboxWithCleanup(t, c) + sbxId := sbx.SandboxID + + res, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode()) + require.NotNil(t, res.JSON200) + assert.Equal(t, api.Running, res.JSON200.State) + + initialEndTime := res.JSON200.EndAt + + // Connect to the sandbox + sbxConnect, err := c.PostSandboxesSandboxIDConnectWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: 321, + }, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, sbxConnect.StatusCode()) + require.NotNil(t, sbxConnect.JSON200) + assert.Equal(t, sbxConnect.JSON200.SandboxID, sbxId) + + // Check if the sandbox is running and the timeout isn't changed + res, err = c.GetSandboxesSandboxIDWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode()) + require.NotNil(t, res.JSON200) + assert.Equal(t, api.Running, res.JSON200.State) + + assert.True(t, res.JSON200.EndAt.After(initialEndTime), "End time should be extended") + }) + + t.Run("connect to not existing sandbox", func(t *testing.T) { + // Try to connect the sandbox + sbxConnect, err := c.PostSandboxesSandboxIDConnectWithResponse(t.Context(), "it-isnt-there", api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: 30, + }, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusNotFound, sbxConnect.StatusCode()) + }) + + t.Run("connect with too big timeout", func(t *testing.T) { + // Try to connect the sandbox + sbxConnect, err := c.PostSandboxesSandboxIDConnectWithResponse(t.Context(), "it-isnt-there", api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: 60 * 60 * 72, // 3 days + }, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusBadRequest, sbxConnect.StatusCode()) + }) + + t.Run("concurrent connects - not returning early", func(t *testing.T) { + c := setup.GetAPIClient() + + // Create a sandbox with auto-pause disabled + sbx := utils.SetupSandboxWithCleanup(t, c) + sbxId := sbx.SandboxID + + // Pause the sandbox + resp, err := c.PostSandboxesSandboxIDPauseWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusNoContent, resp.StatusCode()) + + wg := errgroup.Group{} + for range 5 { + wg.Go(func() error { + // Try to connect the sandbox + sbxConnect, err := c.PostSandboxesSandboxIDConnectWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: 30, + }, setup.WithAPIKey()) + if err != nil { + return fmt.Errorf("connect sandbox - %w", err) + } + + // Try to check the status of the sandbox + sbxState, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + if err != nil { + return fmt.Errorf("get sandbox - %w", err) + } + + if sbxState.StatusCode() != http.StatusOK { + return fmt.Errorf("get sandbox - unexpected status code: %d", sbxState.StatusCode()) + } + + if sbxState.JSON200.State != api.Running { + return fmt.Errorf("get sandbox - unexpected state: %s", sbxState.JSON200.State) + } + + if sbxConnect.StatusCode() != http.StatusCreated && sbxConnect.StatusCode() != http.StatusOK { + return fmt.Errorf("connect sandbox - unexpected status code: %d", sbxConnect.StatusCode()) + } + + return nil + }) + } + + err = wg.Wait() + require.NoError(t, err) + }) +} diff --git a/tests/integration/internal/tests/api/sandboxes/sandbox_refresh_test.go b/tests/integration/internal/tests/api/sandboxes/sandbox_refresh_test.go new file mode 100644 index 0000000000..1514997819 --- /dev/null +++ b/tests/integration/internal/tests/api/sandboxes/sandbox_refresh_test.go @@ -0,0 +1,79 @@ +package sandboxes + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/e2b-dev/infra/tests/integration/internal/api" + "github.com/e2b-dev/infra/tests/integration/internal/setup" + "github.com/e2b-dev/infra/tests/integration/internal/utils" +) + +func TestSandboxRefresh(t *testing.T) { + c := setup.GetAPIClient() + testCases := []struct { + name string + extend bool + same bool + + initialDuration int + newDuration int + }{ + { + name: "extend", + extend: true, + same: false, + + initialDuration: 60, + newDuration: 120, + }, + { + name: "shorten", + extend: false, + same: true, + initialDuration: 120, + newDuration: 60, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithTimeout(int32(tc.initialDuration))) + + // Get initial sandbox details + detailResp, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbx.SandboxID, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, detailResp.StatusCode()) + require.NotNil(t, detailResp.JSON200) + + initialEndTime := detailResp.JSON200.EndAt + + timeoutResp, err := c.PostSandboxesSandboxIDRefreshesWithResponse(t.Context(), sbx.SandboxID, api.PostSandboxesSandboxIDRefreshesJSONRequestBody{ + Duration: &tc.newDuration, + }, setup.WithAPIKey()) + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, timeoutResp.StatusCode()) + + detailResp2, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbx.SandboxID, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, detailResp2.StatusCode()) + require.NotNil(t, detailResp2.JSON200) + + newEndTime := detailResp2.JSON200.EndAt + + assert.Equal(t, tc.extend, newEndTime.After(initialEndTime), "End time should be extended") + assert.Equal(t, tc.same, newEndTime.Equal(initialEndTime), "End time should be updated") + }) + } +} + +func TestSandboxRefresh_NotFound(t *testing.T) { + c := setup.GetAPIClient() + + timeoutResp, err := c.PostSandboxesSandboxIDRefreshesWithResponse(t.Context(), "nonexistent-sandbox-id", api.PostSandboxesSandboxIDRefreshesJSONRequestBody{}, setup.WithAPIKey()) + require.NoError(t, err) + assert.Equal(t, http.StatusNotFound, timeoutResp.StatusCode()) +} diff --git a/tests/integration/internal/tests/api/sandboxes/sandbox_set_timeout_test.go b/tests/integration/internal/tests/api/sandboxes/sandbox_set_timeout_test.go deleted file mode 100644 index 80ae72de92..0000000000 --- a/tests/integration/internal/tests/api/sandboxes/sandbox_set_timeout_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package sandboxes - -import ( - "fmt" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" - - "github.com/e2b-dev/infra/tests/integration/internal/api" - "github.com/e2b-dev/infra/tests/integration/internal/setup" - "github.com/e2b-dev/infra/tests/integration/internal/utils" -) - -func TestSandboxSetTimeoutPausingSandbox(t *testing.T) { - c := setup.GetAPIClient() - - t.Run("test set timeout while pausing", func(t *testing.T) { - sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithAutoPause(true)) - sbxId := sbx.SandboxID - - // Pause the sandbox - wg := errgroup.Group{} - wg.Go(func() error { - pauseResp, err := c.PostSandboxesSandboxIDPauseWithResponse(t.Context(), sbxId, setup.WithAPIKey()) - if err != nil { - return err - } - - if pauseResp.StatusCode() != http.StatusNoContent { - return fmt.Errorf("unexpected status code: %d", pauseResp.StatusCode()) - } - - return nil - }) - - for range 5 { - time.Sleep(200 * time.Millisecond) - wg.Go(func() error { - setTimeoutResp, err := c.PostSandboxesSandboxIDTimeoutWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDTimeoutJSONRequestBody{ - Timeout: 15, - }, - setup.WithAPIKey()) - if err != nil { - return err - } - - if setTimeoutResp.StatusCode() != http.StatusNotFound { - return fmt.Errorf("unexpected status code: %d", setTimeoutResp.StatusCode()) - } - - return nil - }) - } - - err := wg.Wait() - require.NoError(t, err) - }) -} diff --git a/tests/integration/internal/tests/api/sandboxes/sandbox_timeout_test.go b/tests/integration/internal/tests/api/sandboxes/sandbox_timeout_test.go new file mode 100644 index 0000000000..258dda9a36 --- /dev/null +++ b/tests/integration/internal/tests/api/sandboxes/sandbox_timeout_test.go @@ -0,0 +1,126 @@ +package sandboxes + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/e2b-dev/infra/tests/integration/internal/api" + "github.com/e2b-dev/infra/tests/integration/internal/setup" + "github.com/e2b-dev/infra/tests/integration/internal/utils" +) + +func TestSandboxTimeout(t *testing.T) { + c := setup.GetAPIClient() + testCases := []struct { + name string + extend bool + + initialDuration int32 + newDuration int32 + }{ + { + name: "extend", + extend: true, + initialDuration: 60, + newDuration: 120, + }, + { + name: "shorten", + extend: false, + initialDuration: 120, + newDuration: 60, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithTimeout(tc.initialDuration)) + + // Get initial sandbox details + detailResp, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbx.SandboxID, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, detailResp.StatusCode()) + require.NotNil(t, detailResp.JSON200) + + initialEndTime := detailResp.JSON200.EndAt + + timeoutResp, err := c.PostSandboxesSandboxIDTimeoutWithResponse(t.Context(), sbx.SandboxID, api.PostSandboxesSandboxIDTimeoutJSONRequestBody{ + Timeout: tc.newDuration, + }, setup.WithAPIKey()) + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, timeoutResp.StatusCode()) + + detailResp2, err := c.GetSandboxesSandboxIDWithResponse(t.Context(), sbx.SandboxID, setup.WithAPIKey()) + require.NoError(t, err) + require.Equal(t, http.StatusOK, detailResp2.StatusCode()) + require.NotNil(t, detailResp2.JSON200) + + newEndTime := detailResp2.JSON200.EndAt + + assert.Equal(t, tc.extend, newEndTime.After(initialEndTime), "End time should be extended") + assert.NotEqual(t, initialEndTime, newEndTime, "End time should be updated") + }) + } +} + +func TestSandboxTimeout_NotFound(t *testing.T) { + c := setup.GetAPIClient() + + timeoutResp, err := c.PostSandboxesSandboxIDTimeoutWithResponse(t.Context(), "nonexistent-sandbox-id", api.PostSandboxesSandboxIDTimeoutJSONRequestBody{ + Timeout: 60, + }, setup.WithAPIKey()) + require.NoError(t, err) + assert.Equal(t, http.StatusNotFound, timeoutResp.StatusCode()) +} + +func TestSandboxSetTimeoutPausingSandbox(t *testing.T) { + c := setup.GetAPIClient() + + t.Run("test set timeout while pausing", func(t *testing.T) { + sbx := utils.SetupSandboxWithCleanup(t, c, utils.WithAutoPause(true)) + sbxId := sbx.SandboxID + + // Pause the sandbox + wg := errgroup.Group{} + wg.Go(func() error { + pauseResp, err := c.PostSandboxesSandboxIDPauseWithResponse(t.Context(), sbxId, setup.WithAPIKey()) + if err != nil { + return err + } + + if pauseResp.StatusCode() != http.StatusNoContent { + return fmt.Errorf("unexpected status code: %d", pauseResp.StatusCode()) + } + + return nil + }) + + for range 5 { + time.Sleep(200 * time.Millisecond) + wg.Go(func() error { + setTimeoutResp, err := c.PostSandboxesSandboxIDTimeoutWithResponse(t.Context(), sbxId, api.PostSandboxesSandboxIDTimeoutJSONRequestBody{ + Timeout: 15, + }, + setup.WithAPIKey()) + if err != nil { + return err + } + + if setTimeoutResp.StatusCode() != http.StatusNotFound { + return fmt.Errorf("unexpected status code: %d", setTimeoutResp.StatusCode()) + } + + return nil + }) + } + + err := wg.Wait() + require.NoError(t, err) + }) +}