GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix: keep in-flight builds from blocking the prebuild hard limit (#30… · coder/coder@5563b74

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • coderd/database
    • querier.go
    • querier_test.go
    • queries.sql.go
    • queries
      • prebuilds.sql
  • enterprise/coderd/prebuilds
    • reconcile_test.go
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎coderd/database/querier.go‎

Copy file name to clipboardExpand all lines: coderd/database/querier.go
+1-1Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Collapse file

‎coderd/database/querier_test.go‎

Copy file name to clipboardExpand all lines: coderd/database/querier_test.go
+35-5Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7685,6 +7685,7 @@ func createTmplVersionAndPreset(
76857685

76867686
type createPrebuiltWorkspaceOpts struct {
76877687
failedJob bool
7688+
succeededJob bool
76887689
createdAt time.Time
76897690
readyAgents int
76907691
notReadyAgents int
@@ -7705,12 +7706,19 @@ func createPrebuiltWorkspace(
77057706
if opts != nil && opts.failedJob {
77067707
jobError = sql.NullString{String: "failed", Valid: true}
77077708
}
7709+
var jobStartedAt, jobCompletedAt sql.NullTime
7710+
if opts != nil && opts.succeededJob {
7711+
jobStartedAt = sql.NullTime{Time: now.Add(-1 * time.Minute), Valid: true}
7712+
jobCompletedAt = sql.NullTime{Time: now, Valid: true}
7713+
}
77087714
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
77097715
Type: database.ProvisionerJobTypeWorkspaceBuild,
77107716
OrganizationID: orgID,
77117717

7712-
CreatedAt: now.Add(-1 * time.Minute),
7713-
Error: jobError,
7718+
CreatedAt: now.Add(-1 * time.Minute),
7719+
StartedAt: jobStartedAt,
7720+
CompletedAt: jobCompletedAt,
7721+
Error: jobError,
77147722
})
77157723

77167724
// create ready agents
@@ -8717,7 +8725,9 @@ func TestGetPresetsAtFailureLimit(t *testing.T) {
87178725
name string
87188726
// true - build is successful
87198727
// false - build is unsuccessful
8720-
buildSuccesses []bool
8728+
buildSuccesses []bool
8729+
// newestInFlight adds a pending build after buildSuccesses, as the reconciler sees while it is still creating.
8730+
newestInFlight bool
87218731
hardLimit int64
87228732
expHitHardLimit bool
87238733
}{
@@ -8763,6 +8773,20 @@ func TestGetPresetsAtFailureLimit(t *testing.T) {
87638773
hardLimit: 3,
87648774
expHitHardLimit: false,
87658775
},
8776+
{
8777+
name: "last 3 builds are failed and a newer one is in flight - hard limit is reached",
8778+
buildSuccesses: []bool{false, false, false},
8779+
newestInFlight: true,
8780+
hardLimit: 3,
8781+
expHitHardLimit: true,
8782+
},
8783+
{
8784+
name: "last build is successful and a newer one is in flight - hard limit is NOT reached",
8785+
buildSuccesses: []bool{false, false, true},
8786+
newestInFlight: true,
8787+
hardLimit: 3,
8788+
expHitHardLimit: false,
8789+
},
87668790
// hardLimit set to zero, implicitly disables the hard limit.
87678791
{
87688792
name: "despite 5 failed builds, the hard limit is not reached because it's disabled.",
@@ -8789,8 +8813,14 @@ func TestGetPresetsAtFailureLimit(t *testing.T) {
87898813
tmplV1 := createTmplVersionAndPreset(t, db, tmpl, tmpl.ActiveVersionID, now, nil)
87908814
for idx, buildSuccess := range tc.buildSuccesses {
87918815
createPrebuiltWorkspace(ctx, t, db, tmpl, tmplV1, orgID, now, &createPrebuiltWorkspaceOpts{
8792-
failedJob: !buildSuccess,
8793-
createdAt: hourBefore.Add(time.Duration(idx) * time.Second),
8816+
failedJob: !buildSuccess,
8817+
succeededJob: buildSuccess,
8818+
createdAt: hourBefore.Add(time.Duration(idx) * time.Second),
8819+
})
8820+
}
8821+
if tc.newestInFlight {
8822+
createPrebuiltWorkspace(ctx, t, db, tmpl, tmplV1, orgID, now, &createPrebuiltWorkspaceOpts{
8823+
createdAt: hourBefore.Add(time.Duration(len(tc.buildSuccesses)) * time.Second),
87948824
})
87958825
}
87968826

Collapse file

‎coderd/database/queries.sql.go‎

Copy file name to clipboardExpand all lines: coderd/database/queries.sql.go
+3-1Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Collapse file

‎coderd/database/queries/prebuilds.sql‎

Copy file name to clipboardExpand all lines: coderd/database/queries/prebuilds.sql
+3-1Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ GROUP BY tsb.template_version_id, tsb.preset_id, fc.num_failed;
247247

248248
-- GetPresetsAtFailureLimit groups workspace builds by preset ID.
249249
-- Each preset is associated with exactly one template version ID.
250-
-- For each preset, the query checks the last hard_limit builds.
250+
-- For each preset, the query checks the last hard_limit finished builds.
251251
-- If all of them failed, the preset is considered to have hit the hard failure limit.
252252
-- The query returns a list of preset IDs that have reached this failure threshold.
253253
-- Only active template versions with configured presets are considered.
@@ -263,6 +263,8 @@ WITH filtered_builds AS (
263263
WHERE tvp.desired_instances IS NOT NULL -- Consider only presets that have a prebuild configuration.
264264
AND wlb.transition = 'start'::workspace_transition
265265
AND w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'
266+
-- Rank only finished builds, so an in-flight build cannot hold a slot in the last hard_limit.
267+
AND wlb.job_status IN ('succeeded'::provisioner_job_status, 'failed'::provisioner_job_status, 'canceled'::provisioner_job_status)
266268
),
267269
time_sorted_builds AS (
268270
-- Group builds by preset, then sort each group by created_at.
Collapse file

‎enterprise/coderd/prebuilds/reconcile_test.go‎

Copy file name to clipboardExpand all lines: enterprise/coderd/prebuilds/reconcile_test.go
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,59 @@ func TestSkippingHardLimitedPresets(t *testing.T) {
10781078
}
10791079
}
10801080

1081+
func TestHardLimitedPresetWithBuildInFlight(t *testing.T) {
1082+
t.Parallel()
1083+
1084+
clock := quartz.NewMock(t)
1085+
ctx := testutil.Context(t, testutil.WaitShort)
1086+
cfg := codersdk.PrebuildsConfig{
1087+
FailureHardLimit: serpent.Int64(1),
1088+
ReconciliationBackoffInterval: 0,
1089+
}
1090+
logger := slogtest.Make(
1091+
t, &slogtest.Options{IgnoreErrors: true},
1092+
).Leveled(slog.LevelDebug)
1093+
db, pubSub := dbtestutil.NewDB(t)
1094+
cache := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
1095+
controller := prebuilds.NewStoreReconciler(
1096+
db, pubSub, cache, cfg, logger,
1097+
clock,
1098+
prometheus.NewRegistry(),
1099+
newFakeEnqueuer(),
1100+
newNoopUsageCheckerPtr(),
1101+
noop.NewTracerProvider(),
1102+
10,
1103+
nil,
1104+
)
1105+
1106+
ownerID := uuid.New()
1107+
dbgen.User(t, db, database.User{
1108+
ID: ownerID,
1109+
})
1110+
org, template := setupTestDBTemplate(t, db, ownerID, false)
1111+
templateVersionID := setupTestDBTemplateVersion(ctx, t, clock, db, pubSub, org.ID, ownerID, template.ID)
1112+
preset := setupTestDBPreset(t, db, templateVersionID, 2, uuid.New().String())
1113+
1114+
// Given: a failed prebuild, followed by a newer one that is still pending, as the reconciler
1115+
// sees when a failing build outlasts a reconciliation interval.
1116+
setupTestDBPrebuild(t, clock, db, pubSub, database.WorkspaceTransitionStart, database.ProvisionerJobStatusFailed, org.ID, preset, template.ID, templateVersionID)
1117+
clock.Advance(time.Second).MustWait(ctx)
1118+
setupTestDBPrebuild(t, clock, db, pubSub, database.WorkspaceTransitionStart, database.ProvisionerJobStatusPending, org.ID, preset, template.ID, templateVersionID)
1119+
clock.Advance(time.Nanosecond).MustWait(ctx)
1120+
1121+
// When: reconciling.
1122+
_, err := controller.ReconcileAll(ctx)
1123+
require.NoError(t, err)
1124+
1125+
// Then: the in-flight build does not hide the failure, so the preset is hard limited and nothing new is created.
1126+
workspaces, err := db.GetWorkspacesByTemplateID(ctx, template.ID)
1127+
require.NoError(t, err)
1128+
require.Len(t, workspaces, 2)
1129+
updatedPreset, err := db.GetPresetByID(ctx, preset.ID)
1130+
require.NoError(t, err)
1131+
require.Equal(t, database.PrebuildStatusHardLimited, updatedPreset.PrebuildStatus)
1132+
}
1133+
10811134
func TestValidationFailedPresets(t *testing.T) {
10821135
t.Parallel()
10831136

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.