Skip to content
Merged
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
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,19 @@ copy-public-builds:


.PHONY: generate
generate: generate/api generate/orchestrator generate/client-proxy generate/envd generate/db
generate: generate/api generate/orchestrator generate/client-proxy generate/envd generate/db generate-tests
generate/%:
@echo "Generating code for *$(notdir $@)*"
$(MAKE) -C packages/$(notdir $@) generate
@printf "\n\n"

.PHONY: generate-tests
generate-tests: generate-tests/integration
generate-tests/%:
@echo "Generating code for *$(notdir $@)*"
$(MAKE) -C tests/$(notdir $@) generate
@printf "\n\n"

.PHONY: migrate
migrate:
$(MAKE) -C packages/db migrate/up
Expand Down
185 changes: 93 additions & 92 deletions packages/api/internal/api/spec.gen.go

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions packages/api/internal/api/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions packages/api/internal/cache/templates/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/e2b-dev/infra/packages/shared/pkg/db"
"github.com/e2b-dev/infra/packages/shared/pkg/logger"
"github.com/e2b-dev/infra/packages/shared/pkg/models/envbuild"
"github.com/e2b-dev/infra/packages/shared/pkg/schema"
)

const templateInfoExpiration = 5 * time.Minute
Expand Down Expand Up @@ -154,7 +155,7 @@ type TemplateBuildInfo struct {
TeamID uuid.UUID
TemplateID string
BuildStatus envbuild.Status
Reason *string
Reason *schema.BuildReason

ClusterID *uuid.UUID
ClusterNodeID *string
Expand Down Expand Up @@ -182,7 +183,7 @@ func NewTemplateBuildCache(db *db.DB) *TemplatesBuildCache {
}
}

func (c *TemplatesBuildCache) SetStatus(buildID uuid.UUID, status envbuild.Status, reason *string) {
func (c *TemplatesBuildCache) SetStatus(buildID uuid.UUID, status envbuild.Status, reason *schema.BuildReason) {
c.mx.Lock()
defer c.mx.Unlock()

Expand All @@ -197,7 +198,7 @@ func (c *TemplatesBuildCache) SetStatus(buildID uuid.UUID, status envbuild.Statu
logger.WithBuildID(buildID.String()),
zap.String("to_status", status.String()),
zap.String("from_status", item.BuildStatus.String()),
zap.Stringp("reason", reason),
zap.Any("reason", reason),
)

_ = c.cache.Set(
Expand Down
14 changes: 13 additions & 1 deletion packages/api/internal/handlers/template_build_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/e2b-dev/infra/packages/shared/pkg/db"
"github.com/e2b-dev/infra/packages/shared/pkg/logs"
"github.com/e2b-dev/infra/packages/shared/pkg/models/envbuild"
"github.com/e2b-dev/infra/packages/shared/pkg/schema"
"github.com/e2b-dev/infra/packages/shared/pkg/telemetry"
)

Expand Down Expand Up @@ -79,7 +80,7 @@ func (a *APIStore) GetTemplatesTemplateIDBuildsBuildIDStatus(c *gin.Context, tem
TemplateID: templateID,
BuildID: buildID,
Status: getCorrespondingTemplateBuildStatus(buildInfo.BuildStatus),
Reason: buildInfo.Reason,
Reason: getAPIReason(buildInfo.Reason),
}

cli, err := a.templateManager.GetBuildClient(team.ClusterID, buildInfo.ClusterNodeID, false)
Expand Down Expand Up @@ -124,6 +125,17 @@ func getCorrespondingTemplateBuildStatus(s envbuild.Status) api.TemplateBuildSta
}
}

func getAPIReason(reason *schema.BuildReason) *api.BuildStatusReason {
if reason == nil {
return nil
}

return &api.BuildStatusReason{
Message: reason.Message,
Step: reason.Step,
}
}

func apiToLogLevel(level *api.LogLevel) *logs.LogLevel {
if level == nil {
return nil
Expand Down
2 changes: 1 addition & 1 deletion packages/api/internal/orchestrator/pause_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (o *Orchestrator) PauseInstance(
return fmt.Errorf("error pausing sandbox: %w", err)
}

err = o.dbClient.EnvBuildSetStatus(ctx, *envBuild.EnvID, envBuild.ID, envbuild.StatusSuccess)
err = o.dbClient.EnvBuildSetStatus(ctx, *envBuild.EnvID, envBuild.ID, envbuild.StatusSuccess, nil)
if err != nil {
telemetry.ReportCriticalError(ctx, "error pausing sandbox", err)

Expand Down
5 changes: 3 additions & 2 deletions packages/api/internal/template-manager/create_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ func (tm *TemplateManager) CreateTemplate(

// Report build failur status on any error while creating the template
telemetry.ReportCriticalError(ctx, "build failed", e, telemetry.WithTemplateID(templateID))
msg := fmt.Sprintf("error when building env: %s", e)
err := tm.SetStatus(
ctx,
templateID,
buildID,
envbuild.StatusFailed,
&msg,
&templatemanagergrpc.TemplateBuildStatusReason{
Message: fmt.Sprintf("error when building env: %s", e),
},
)
if err != nil {
e = errors.Join(e, fmt.Errorf("failed to set build status to failed: %w", err))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type fakeTemplateManagerClient struct {
getStatusErr error
}

func (f fakeTemplateManagerClient) SetStatus(ctx context.Context, templateID string, buildID uuid.UUID, status envbuild.Status, reason *string) error {
func (f fakeTemplateManagerClient) SetStatus(ctx context.Context, templateID string, buildID uuid.UUID, status envbuild.Status, reason *templatemanagergrpc.TemplateBuildStatusReason) error {
return f.setStatusError
}

Expand All @@ -35,7 +35,7 @@ func (f fakeTemplateManagerClient) GetStatus(ctx context.Context, buildID uuid.U
func TestPollBuildStatus_setStatus(t *testing.T) {
type fields struct {
buildID uuid.UUID
templateManagerClient *fakeTemplateManagerClient
templateManagerClient templateManagerClient
}

tests := []struct {
Expand Down Expand Up @@ -120,7 +120,7 @@ func TestPollBuildStatus_setStatus(t *testing.T) {

func TestPollBuildStatus_dispatchBasedOnStatus(t *testing.T) {
type fields struct {
templateManagerClient *fakeTemplateManagerClient
templateManagerClient templateManagerClient
}
type args struct {
status *templatemanagergrpc.TemplateBuildStatusResponse
Expand Down Expand Up @@ -272,7 +272,7 @@ func TestPollBuildStatus_dispatchBasedOnStatus(t *testing.T) {
logger: zap.NewNop(),
}

err, completed := c.dispatchBasedOnStatus(context.TODO(), tt.args.status)
completed, err := c.dispatchBasedOnStatus(context.TODO(), tt.args.status)
if tt.wantErr {
if err == nil {
t.Errorf("Expected error, got no error")
Expand Down
68 changes: 42 additions & 26 deletions packages/api/internal/template-manager/template_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
templatemanagergrpc "github.com/e2b-dev/infra/packages/shared/pkg/grpc/template-manager"
"github.com/e2b-dev/infra/packages/shared/pkg/logger"
"github.com/e2b-dev/infra/packages/shared/pkg/models/envbuild"
"github.com/e2b-dev/infra/packages/shared/pkg/schema"
)

var (
Expand All @@ -39,8 +40,9 @@ func (tm *TemplateManager) BuildStatusSync(ctx context.Context, buildID uuid.UUI
if envBuildDb.Status == envbuild.StatusWaiting {
// if waiting for too long, fail the build
if time.Since(envBuildDb.CreatedAt) > syncWaitingStateDeadline {
msg := "build is in waiting state for too long"
err = tm.SetStatus(ctx, templateID, buildID, envbuild.StatusFailed, &msg)
err = tm.SetStatus(ctx, templateID, buildID, envbuild.StatusFailed, &templatemanagergrpc.TemplateBuildStatusReason{
Message: "build is in waiting state for too long",
})
return fmt.Errorf("build is in waiting state for too long, failing it: %w", err)
}

Expand Down Expand Up @@ -68,7 +70,7 @@ func (tm *TemplateManager) BuildStatusSync(ctx context.Context, buildID uuid.UUI
}

type templateManagerClient interface {
SetStatus(ctx context.Context, templateID string, buildID uuid.UUID, status envbuild.Status, reason *string) error
SetStatus(ctx context.Context, templateID string, buildID uuid.UUID, status envbuild.Status, reason *templatemanagergrpc.TemplateBuildStatusReason) error
SetFinished(ctx context.Context, templateID string, buildID uuid.UUID, rootfsSize int64, envdVersion string) error
GetStatus(ctx context.Context, buildId uuid.UUID, templateID string, clusterID *uuid.UUID, nodeID *string) (*templatemanagergrpc.TemplateBuildStatusResponse, error)
}
Expand All @@ -95,8 +97,9 @@ func (c *PollBuildStatus) poll(ctx context.Context) {
case <-ctx.Done():
c.logger.Debug("Build status polling timed out, stopping polling")

reason := fmt.Sprintf("build status polling timed out. Maximum build time is %s.", buildTimeout)
statusErr := c.client.SetStatus(ctx, c.templateID, c.buildID, envbuild.StatusFailed, &reason)
statusErr := c.client.SetStatus(ctx, c.templateID, c.buildID, envbuild.StatusFailed, &templatemanagergrpc.TemplateBuildStatusReason{
Message: fmt.Sprintf("build status polling timed out. Maximum build time is %s.", buildTimeout),
})
if statusErr != nil {
c.logger.Error("error when setting build status", zap.Error(statusErr))
}
Expand All @@ -105,12 +108,13 @@ func (c *PollBuildStatus) poll(ctx context.Context) {
case <-ticker.C:
c.logger.Debug("Checking template build status")

err, buildCompleted := c.checkBuildStatus(ctx)
buildCompleted, err := c.checkBuildStatus(ctx)
if err != nil {
c.logger.Error("Build status polling received unrecoverable error", zap.Error(err))

reason := fmt.Sprintf("polling received unrecoverable error: %s", err)
statusErr := c.client.SetStatus(ctx, c.templateID, c.buildID, envbuild.StatusFailed, &reason)
statusErr := c.client.SetStatus(ctx, c.templateID, c.buildID, envbuild.StatusFailed, &templatemanagergrpc.TemplateBuildStatusReason{
Message: fmt.Sprintf("polling received unrecoverable error: %s", err),
})
if statusErr != nil {
c.logger.Error("error when setting build status", zap.Error(statusErr))
}
Expand Down Expand Up @@ -162,37 +166,37 @@ func (c *PollBuildStatus) setStatus(ctx context.Context) error {
return nil
}

func (c *PollBuildStatus) dispatchBasedOnStatus(ctx context.Context, status *templatemanagergrpc.TemplateBuildStatusResponse) (error, bool) {
func (c *PollBuildStatus) dispatchBasedOnStatus(ctx context.Context, status *templatemanagergrpc.TemplateBuildStatusResponse) (bool, error) {
if status == nil {
return errors.New("nil status"), false
return false, errors.New("nil status")
}
switch status.GetStatus() {
case templatemanagergrpc.TemplateBuildState_Failed:
// build failed
err := c.client.SetStatus(ctx, c.templateID, c.buildID, envbuild.StatusFailed, status.Reason)
if err != nil {
return errors.Wrap(err, "error when setting build status"), false
return false, errors.Wrap(err, "error when setting build status")
}
return nil, true
return true, nil
case templatemanagergrpc.TemplateBuildState_Completed:
// build completed
meta := status.GetMetadata()
if meta == nil {
return errors.New("nil metadata"), false
return false, errors.New("nil metadata")
}

err := c.client.SetFinished(ctx, c.templateID, c.buildID, int64(meta.RootfsSizeKey), meta.EnvdVersionKey)
if err != nil {
return errors.Wrap(err, "error when finishing build"), false
return false, errors.Wrap(err, "error when finishing build")
}
return nil, true
return true, nil
default:
c.logger.Debug("skipping status", zap.Any("status", status))
return nil, false
return false, nil
}
}

func (c *PollBuildStatus) checkBuildStatus(ctx context.Context) (error, bool) {
func (c *PollBuildStatus) checkBuildStatus(ctx context.Context) (bool, error) {
c.logger.Debug("Checking template build status")

retrier := retry.NewRetrier(
Expand All @@ -204,17 +208,17 @@ func (c *PollBuildStatus) checkBuildStatus(ctx context.Context) (error, bool) {
err := retrier.RunContext(ctx, c.setStatus)
if err != nil {
c.logger.Error("error when calling setStatus", zap.Error(err))
return err, false
return false, err
}

c.logger.Debug("dispatching based on status", zap.Any("status", c.status))

err, buildCompleted := c.dispatchBasedOnStatus(ctx, c.status)
buildCompleted, err := c.dispatchBasedOnStatus(ctx, c.status)
if err != nil {
return errors.Wrap(err, "error when dispatching build status"), false
return false, errors.Wrap(err, "error when dispatching build status")
}

return nil, buildCompleted
return buildCompleted, nil
}

func (tm *TemplateManager) removeFromProcessingQueue(buildID uuid.UUID) {
Expand All @@ -237,19 +241,31 @@ func (tm *TemplateManager) createInProcessingQueue(buildID uuid.UUID, templateID
return false
}

func (tm *TemplateManager) SetStatus(ctx context.Context, templateID string, buildID uuid.UUID, status envbuild.Status, reason *string) error {
func (tm *TemplateManager) SetStatus(ctx context.Context, templateID string, buildID uuid.UUID, status envbuild.Status, reason *templatemanagergrpc.TemplateBuildStatusReason) error {
var buildReason *schema.BuildReason
if reason != nil {
buildReason = &schema.BuildReason{
Message: reason.GetMessage(),
}
if step := reason.GetStep(); step != "" {
buildReason.Step = &step
}
}

// first do database update to prevent race condition while calling status
err := tm.db.EnvBuildSetStatus(ctx, templateID, buildID, status)
tm.buildCache.SetStatus(buildID, status, reason)
err := tm.db.EnvBuildSetStatus(ctx, templateID, buildID, status, buildReason)

tm.buildCache.SetStatus(buildID, status, buildReason)
return err
}

func (tm *TemplateManager) SetFinished(ctx context.Context, templateID string, buildID uuid.UUID, rootfsSize int64, envdVersion string) error {
// first do database update to prevent race condition while calling status
err := tm.db.FinishEnvBuild(ctx, templateID, buildID, rootfsSize, envdVersion)
if err != nil {
msg := fmt.Sprintf("error when finishing build: %s", err.Error())
tm.buildCache.SetStatus(buildID, envbuild.StatusFailed, &msg)
tm.buildCache.SetStatus(buildID, envbuild.StatusFailed, &schema.BuildReason{
Message: fmt.Sprintf("error when finishing build: %s", err.Error()),
})
return err
}

Expand Down
2 changes: 1 addition & 1 deletion packages/db/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ build-debug:
.PHONE: create-migration
create-migration:
ifeq ($(origin NAME), undefined)
@echo "The expected syntax is: make migration-create NAME=your-migration-name"
@echo "The expected syntax is: make create-migration NAME=your-migration-name"
@exit 1
endif
@$(goose) create $(NAME) sql
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- +goose Up
-- +goose StatementBegin

-- First, update existing TEXT values to JSON format
-- Convert existing text reasons to JSON with message field
UPDATE env_builds
SET reason = json_build_object('message', reason)::jsonb
WHERE reason IS NOT NULL AND reason != '';

-- Now alter the column type from TEXT to JSONB
ALTER TABLE env_builds
ALTER COLUMN reason TYPE jsonb
USING CASE
WHEN reason IS NULL THEN NULL
WHEN reason::text = '' THEN NULL
ELSE reason::jsonb
END;

-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin

-- Convert JSONB back to TEXT by extracting the message field
ALTER TABLE env_builds
ALTER COLUMN reason TYPE text
USING CASE
WHEN reason IS NULL THEN NULL
WHEN reason->>'message' IS NOT NULL THEN reason->>'message'
ELSE reason::text
END;

-- +goose StatementEnd
Loading
Loading