Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8b6e286

Browse files
authored
refactor(coderd): collapse activityBumpWorkspace into a single query (#9652)
* Adds unit-style tests for activityBumpWorkspace* Ports logic of activityBumpWorkspace to a SQL query* Updates activityBumpWorkspace to call above query
1 parent38560dd commit8b6e286

File tree

9 files changed

+424
-69
lines changed

9 files changed

+424
-69
lines changed

‎coderd/activitybump.go‎

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ package coderd
22

33
import (
44
"context"
5-
"database/sql"
6-
"errors"
75
"time"
86

97
"github.com/google/uuid"
108
"golang.org/x/xerrors"
119

1210
"cdr.dev/slog"
1311
"github.com/coder/coder/v2/coderd/database"
14-
"github.com/coder/coder/v2/coderd/database/dbtime"
1512
)
1613

1714
// activityBumpWorkspace automatically bumps the workspace's auto-off timer
@@ -21,72 +18,7 @@ func activityBumpWorkspace(ctx context.Context, log slog.Logger, db database.Sto
2118
// low priority operations fail first.
2219
ctx,cancel:=context.WithTimeout(ctx,time.Second*15)
2320
defercancel()
24-
25-
err:=db.InTx(func(s database.Store)error {
26-
build,err:=s.GetLatestWorkspaceBuildByWorkspaceID(ctx,workspaceID)
27-
iferrors.Is(err,sql.ErrNoRows) {
28-
returnnil
29-
}elseiferr!=nil {
30-
returnxerrors.Errorf("get latest workspace build: %w",err)
31-
}
32-
33-
job,err:=s.GetProvisionerJobByID(ctx,build.JobID)
34-
iferr!=nil {
35-
returnxerrors.Errorf("get provisioner job: %w",err)
36-
}
37-
38-
ifbuild.Transition!=database.WorkspaceTransitionStart||!job.CompletedAt.Valid {
39-
returnnil
40-
}
41-
42-
ifbuild.Deadline.IsZero() {
43-
// Workspace shutdown is manual
44-
returnnil
45-
}
46-
47-
workspace,err:=s.GetWorkspaceByID(ctx,workspaceID)
48-
iferr!=nil {
49-
returnxerrors.Errorf("get workspace: %w",err)
50-
}
51-
52-
var (
53-
// We bump by the original TTL to prevent counter-intuitive behavior
54-
// as the TTL wraps. For example, if I set the TTL to 12 hours, sign off
55-
// work at midnight, come back at 10am, I would want another full day
56-
// of uptime. In the prior implementation, the workspace would enter
57-
// a state of always expiring 1 hour in the future
58-
bumpAmount=time.Duration(workspace.Ttl.Int64)
59-
// DB writes are expensive so we only bump when 5% of the deadline
60-
// has elapsed.
61-
bumpEvery=bumpAmount/20
62-
timeSinceLastBump=bumpAmount-time.Until(build.Deadline)
63-
)
64-
65-
iftimeSinceLastBump<bumpEvery {
66-
returnnil
67-
}
68-
69-
ifbumpAmount==0 {
70-
returnnil
71-
}
72-
73-
newDeadline:=dbtime.Now().Add(bumpAmount)
74-
if!build.MaxDeadline.IsZero()&&newDeadline.After(build.MaxDeadline) {
75-
newDeadline=build.MaxDeadline
76-
}
77-
78-
iferr:=s.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
79-
ID:build.ID,
80-
UpdatedAt:dbtime.Now(),
81-
ProvisionerState:build.ProvisionerState,
82-
Deadline:newDeadline,
83-
MaxDeadline:build.MaxDeadline,
84-
});err!=nil {
85-
returnxerrors.Errorf("update workspace build: %w",err)
86-
}
87-
returnnil
88-
},nil)
89-
iferr!=nil {
21+
iferr:=db.ActivityBumpWorkspace(ctx,workspaceID);err!=nil {
9022
if!xerrors.Is(err,context.Canceled)&&!database.IsQueryCanceledError(err) {
9123
// Bump will fail if the context is canceled, but this is ok.
9224
log.Error(ctx,"bump failed",slog.Error(err),
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package coderd
2+
3+
import (
4+
"database/sql"
5+
"runtime"
6+
"testing"
7+
"time"
8+
9+
"github.com/google/uuid"
10+
11+
"cdr.dev/slog/sloggers/slogtest"
12+
"github.com/coder/coder/v2/coderd/database"
13+
"github.com/coder/coder/v2/coderd/database/dbgen"
14+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
15+
"github.com/coder/coder/v2/coderd/database/dbtime"
16+
"github.com/coder/coder/v2/coderd/util/ptr"
17+
"github.com/coder/coder/v2/testutil"
18+
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
funcTest_ActivityBumpWorkspace(t*testing.T) {
23+
t.Parallel()
24+
25+
for_,tt:=range []struct {
26+
namestring
27+
transition database.WorkspaceTransition
28+
jobCompletedAt sql.NullTime
29+
buildDeadlineOffset*time.Duration
30+
maxDeadlineOffset*time.Duration
31+
workspaceTTL time.Duration
32+
expectedBump time.Duration
33+
}{
34+
{
35+
name:"NotFinishedYet",
36+
transition:database.WorkspaceTransitionStart,
37+
jobCompletedAt: sql.NullTime{},
38+
buildDeadlineOffset:ptr.Ref(8*time.Hour),
39+
workspaceTTL:8*time.Hour,
40+
expectedBump:0,
41+
},
42+
{
43+
name:"ManualShutdown",
44+
transition:database.WorkspaceTransitionStart,
45+
jobCompletedAt: sql.NullTime{Valid:true,Time:dbtime.Now()},
46+
buildDeadlineOffset:nil,
47+
expectedBump:0,
48+
},
49+
{
50+
name:"NotTimeToBumpYet",
51+
transition:database.WorkspaceTransitionStart,
52+
jobCompletedAt: sql.NullTime{Valid:true,Time:dbtime.Now()},
53+
buildDeadlineOffset:ptr.Ref(8*time.Hour),
54+
workspaceTTL:8*time.Hour,
55+
expectedBump:0,
56+
},
57+
{
58+
name:"TimeToBump",
59+
transition:database.WorkspaceTransitionStart,
60+
jobCompletedAt: sql.NullTime{Valid:true,Time:dbtime.Now().Add(-24*time.Minute)},
61+
buildDeadlineOffset:ptr.Ref(8*time.Hour-24*time.Minute),
62+
workspaceTTL:8*time.Hour,
63+
expectedBump:8*time.Hour,
64+
},
65+
{
66+
name:"MaxDeadline",
67+
transition:database.WorkspaceTransitionStart,
68+
jobCompletedAt: sql.NullTime{Valid:true,Time:dbtime.Now().Add(-24*time.Minute)},
69+
buildDeadlineOffset:ptr.Ref(time.Minute),// last chance to bump!
70+
maxDeadlineOffset:ptr.Ref(time.Hour),
71+
workspaceTTL:8*time.Hour,
72+
expectedBump:1*time.Hour,
73+
},
74+
{
75+
// A workspace that is still running, has passed its deadline, but has not
76+
// yet been auto-stopped should still bump the deadline.
77+
name:"PastDeadlineStillBumps",
78+
transition:database.WorkspaceTransitionStart,
79+
jobCompletedAt: sql.NullTime{Valid:true,Time:dbtime.Now().Add(-24*time.Minute)},
80+
buildDeadlineOffset:ptr.Ref(-time.Minute),
81+
workspaceTTL:8*time.Hour,
82+
expectedBump:8*time.Hour,
83+
},
84+
{
85+
// A stopped workspace should never bump.
86+
name:"StoppedWorkspace",
87+
transition:database.WorkspaceTransitionStop,
88+
jobCompletedAt: sql.NullTime{Valid:true,Time:dbtime.Now().Add(-time.Minute)},
89+
buildDeadlineOffset:ptr.Ref(-time.Minute),
90+
workspaceTTL:8*time.Hour,
91+
expectedBump:0,
92+
},
93+
} {
94+
tt:=tt
95+
t.Run(tt.name,func(t*testing.T) {
96+
t.Parallel()
97+
98+
var (
99+
now=dbtime.Now()
100+
ctx=testutil.Context(t,testutil.WaitShort)
101+
log=slogtest.Make(t,nil)
102+
db,_=dbtestutil.NewDB(t)
103+
org=dbgen.Organization(t,db, database.Organization{})
104+
user=dbgen.User(t,db, database.User{
105+
Status:database.UserStatusActive,
106+
})
107+
_=dbgen.OrganizationMember(t,db, database.OrganizationMember{
108+
UserID:user.ID,
109+
OrganizationID:org.ID,
110+
})
111+
templateVersion=dbgen.TemplateVersion(t,db, database.TemplateVersion{
112+
OrganizationID:org.ID,
113+
CreatedBy:user.ID,
114+
})
115+
template=dbgen.Template(t,db, database.Template{
116+
OrganizationID:org.ID,
117+
ActiveVersionID:templateVersion.ID,
118+
CreatedBy:user.ID,
119+
})
120+
ws=dbgen.Workspace(t,db, database.Workspace{
121+
OwnerID:user.ID,
122+
OrganizationID:org.ID,
123+
TemplateID:template.ID,
124+
Ttl: sql.NullInt64{Valid:true,Int64:int64(tt.workspaceTTL)},
125+
})
126+
job=dbgen.ProvisionerJob(t,db, database.ProvisionerJob{
127+
OrganizationID:org.ID,
128+
CompletedAt:tt.jobCompletedAt,
129+
})
130+
_=dbgen.WorkspaceResource(t,db, database.WorkspaceResource{
131+
JobID:job.ID,
132+
})
133+
buildID=uuid.New()
134+
)
135+
136+
varbuildNumberint32=1
137+
// Insert a number of previous workspace builds.
138+
fori:=0;i<5;i++ {
139+
insertPrevWorkspaceBuild(t,db,org.ID,templateVersion.ID,ws.ID,database.WorkspaceTransitionStart,buildNumber)
140+
buildNumber++
141+
insertPrevWorkspaceBuild(t,db,org.ID,templateVersion.ID,ws.ID,database.WorkspaceTransitionStop,buildNumber)
142+
buildNumber++
143+
}
144+
145+
// dbgen.WorkspaceBuild automatically sets deadline to now+1 hour if not set
146+
varbuildDeadline time.Time
147+
iftt.buildDeadlineOffset!=nil {
148+
buildDeadline=now.Add(*tt.buildDeadlineOffset)
149+
}
150+
varmaxDeadline time.Time
151+
iftt.maxDeadlineOffset!=nil {
152+
maxDeadline=now.Add(*tt.maxDeadlineOffset)
153+
}
154+
err:=db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
155+
ID:buildID,
156+
CreatedAt:dbtime.Now(),
157+
UpdatedAt:dbtime.Now(),
158+
BuildNumber:buildNumber,
159+
InitiatorID:user.ID,
160+
Reason:database.BuildReasonInitiator,
161+
WorkspaceID:ws.ID,
162+
JobID:job.ID,
163+
TemplateVersionID:templateVersion.ID,
164+
Transition:tt.transition,
165+
Deadline:buildDeadline,
166+
MaxDeadline:maxDeadline,
167+
})
168+
require.NoError(t,err,"unexpected error inserting workspace build")
169+
bld,err:=db.GetWorkspaceBuildByID(ctx,buildID)
170+
require.NoError(t,err,"unexpected error fetching inserted workspace build")
171+
172+
// Validate our initial state before bump
173+
require.Equal(t,tt.transition,bld.Transition,"unexpected transition before bump")
174+
require.Equal(t,tt.jobCompletedAt.Time.UTC(),job.CompletedAt.Time.UTC(),"unexpected job completed at before bump")
175+
require.Equal(t,buildDeadline.UTC(),bld.Deadline.UTC(),"unexpected build deadline before bump")
176+
require.Equal(t,maxDeadline.UTC(),bld.MaxDeadline.UTC(),"unexpected max deadline before bump")
177+
require.Equal(t,tt.workspaceTTL,time.Duration(ws.Ttl.Int64),"unexpected workspace TTL before bump")
178+
179+
workaroundWindowsTimeResolution(t)
180+
181+
// Bump duration is measured from the time of the bump, so we measure from here.
182+
start:=dbtime.Now()
183+
activityBumpWorkspace(ctx,log,db,bld.WorkspaceID)
184+
elapsed:=time.Since(start)
185+
ifelapsed>15*time.Second {
186+
t.Logf("warning: activityBumpWorkspace took longer than 15 seconds: %s",elapsed)
187+
}
188+
// The actual bump could have happened anywhere in the elapsed time, so we
189+
// guess at the approximate time of the bump.
190+
approxBumpTime:=start.Add(elapsed/2)
191+
192+
// Validate our state after bump
193+
updatedBuild,err:=db.GetLatestWorkspaceBuildByWorkspaceID(ctx,bld.WorkspaceID)
194+
require.NoError(t,err,"unexpected error getting latest workspace build")
195+
iftt.expectedBump==0 {
196+
require.Equal(t,bld.UpdatedAt.UTC(),updatedBuild.UpdatedAt.UTC(),"should not have bumped updated_at")
197+
require.Equal(t,bld.Deadline.UTC(),updatedBuild.Deadline.UTC(),"should not have bumped deadline")
198+
}else {
199+
require.NotEqual(t,bld.UpdatedAt.UTC(),updatedBuild.UpdatedAt.UTC(),"should have bumped updated_at")
200+
expectedDeadline:=approxBumpTime.Add(tt.expectedBump).UTC()
201+
// Note: if CI is especially slow, this test may fail. There is an internal 15-second
202+
// deadline in activityBumpWorkspace, so we allow the same window here.
203+
require.WithinDuration(t,expectedDeadline,updatedBuild.Deadline.UTC(),15*time.Second,"unexpected deadline after bump")
204+
}
205+
})
206+
}
207+
}
208+
209+
funcinsertPrevWorkspaceBuild(t*testing.T,db database.Store,orgID,tvID,workspaceID uuid.UUID,transition database.WorkspaceTransition,buildNumberint32) {
210+
t.Helper()
211+
212+
job:=dbgen.ProvisionerJob(t,db, database.ProvisionerJob{
213+
OrganizationID:orgID,
214+
})
215+
_=dbgen.WorkspaceResource(t,db, database.WorkspaceResource{
216+
JobID:job.ID,
217+
})
218+
_=dbgen.WorkspaceBuild(t,db, database.WorkspaceBuild{
219+
BuildNumber:buildNumber,
220+
WorkspaceID:workspaceID,
221+
JobID:job.ID,
222+
TemplateVersionID:tvID,
223+
Transition:transition,
224+
})
225+
}
226+
227+
funcworkaroundWindowsTimeResolution(t*testing.T) {
228+
t.Helper()
229+
ifruntime.GOOS=="windows" {
230+
t.Logf("workaround: sleeping for a short time to avoid time resolution issues on Windows")
231+
<-time.After(testutil.IntervalSlow)
232+
}
233+
}

‎coderd/database/dbauthz/dbauthz.go‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,13 @@ func (q *querier) AcquireProvisionerJob(ctx context.Context, arg database.Acquir
657657
returnq.db.AcquireProvisionerJob(ctx,arg)
658658
}
659659

660+
func (q*querier)ActivityBumpWorkspace(ctx context.Context,arg uuid.UUID)error {
661+
fetch:=func(ctx context.Context,arg uuid.UUID) (database.Workspace,error) {
662+
returnq.db.GetWorkspaceByID(ctx,arg)
663+
}
664+
returnupdate(q.log,q.auth,fetch,q.db.ActivityBumpWorkspace)(ctx,arg)
665+
}
666+
660667
func (q*querier)CleanTailnetCoordinators(ctx context.Context)error {
661668
iferr:=q.authorizeContext(ctx,rbac.ActionDelete,rbac.ResourceTailnetCoordinator);err!=nil {
662669
returnerr

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp