Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit df2649e

Browse files
authored
fix: Test flake in TestWorkspaceStatus (#4333)
This also changes the status to be on the workspace build, since that's where the true value is calculated. This exposed a bug where jobs could never enter the canceled state unless fetched by a provisioner daemon, which was nice to fix! See: https://github.com/coder/coder/actions/runs/3175304200/jobs/5173479506
1 parent d11d83c commit df2649e

13 files changed

+148
-143
lines changed

coderd/database/databasefake/databasefake.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,6 +2238,7 @@ func (q *fakeQuerier) UpdateProvisionerJobWithCancelByID(_ context.Context, arg
22382238
continue
22392239
}
22402240
job.CanceledAt = arg.CanceledAt
2241+
job.CompletedAt = arg.CompletedAt
22412242
q.provisionerJobs[index] = job
22422243
return nil
22432244
}

coderd/database/queries.sql.go

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/provisionerjobs.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ WHERE
7878
UPDATE
7979
provisioner_jobs
8080
SET
81-
canceled_at = $2
81+
canceled_at = $2,
82+
completed_at = $3
8283
WHERE
8384
id = $1;
8485

coderd/templateversions.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Reque
8585
Time: database.Now(),
8686
Valid: true,
8787
},
88+
CompletedAt: sql.NullTime{
89+
Time: database.Now(),
90+
// If the job is running, don't mark it completed!
91+
Valid: !job.WorkerID.Valid,
92+
},
8893
})
8994
if err != nil {
9095
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -339,6 +344,11 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http
339344
Time: database.Now(),
340345
Valid: true,
341346
},
347+
CompletedAt: sql.NullTime{
348+
Time: database.Now(),
349+
// If the job is running, don't mark it completed!
350+
Valid: !job.WorkerID.Valid,
351+
},
342352
})
343353
if err != nil {
344354
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{

coderd/templateversions_test.go

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -715,29 +715,12 @@ func TestTemplateVersionDryRun(t *testing.T) {
715715
ParameterValues: []codersdk.CreateParameterRequest{},
716716
})
717717
require.NoError(t, err)
718-
719-
require.Eventually(t, func() bool {
720-
job, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID)
721-
if !assert.NoError(t, err) {
722-
return false
723-
}
724-
725-
t.Logf("Status: %s", job.Status)
726-
return job.Status == codersdk.ProvisionerJobPending
727-
}, testutil.WaitShort, testutil.IntervalFast)
728-
718+
require.Equal(t, codersdk.ProvisionerJobPending, job.Status)
729719
err = client.CancelTemplateVersionDryRun(ctx, version.ID, job.ID)
730720
require.NoError(t, err)
731-
732-
require.Eventually(t, func() bool {
733-
job, err := client.TemplateVersionDryRun(ctx, version.ID, job.ID)
734-
if !assert.NoError(t, err) {
735-
return false
736-
}
737-
738-
t.Logf("Status: %s", job.Status)
739-
return job.Status == codersdk.ProvisionerJobCanceling
740-
}, testutil.WaitShort, testutil.IntervalFast)
721+
job, err = client.TemplateVersionDryRun(ctx, version.ID, job.ID)
722+
require.NoError(t, err)
723+
require.Equal(t, codersdk.ProvisionerJobCanceled, job.Status)
741724
})
742725

743726
t.Run("AlreadyCompleted", func(t *testing.T) {

coderd/workspacebuilds.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,11 @@ func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
552552
Time: database.Now(),
553553
Valid: true,
554554
},
555+
CompletedAt: sql.NullTime{
556+
Time: database.Now(),
557+
// If the job is running, don't mark it completed!
558+
Valid: !job.WorkerID.Valid,
559+
},
555560
})
556561
if err != nil {
557562
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -835,7 +840,8 @@ func (api *API) convertWorkspaceBuild(
835840
metadata := append(make([]database.WorkspaceResourceMetadatum, 0), metadataByResourceID[resource.ID]...)
836841
apiResources = append(apiResources, convertWorkspaceResource(resource, apiAgents, metadata))
837842
}
838-
843+
apiJob := convertProvisionerJob(job)
844+
transition := codersdk.WorkspaceTransition(build.Transition)
839845
return codersdk.WorkspaceBuild{
840846
ID: build.ID,
841847
CreatedAt: build.CreatedAt,
@@ -846,13 +852,14 @@ func (api *API) convertWorkspaceBuild(
846852
WorkspaceName: workspace.Name,
847853
TemplateVersionID: build.TemplateVersionID,
848854
BuildNumber: build.BuildNumber,
849-
Transition: codersdk.WorkspaceTransition(build.Transition),
855+
Transition: transition,
850856
InitiatorID: build.InitiatorID,
851857
InitiatorUsername: initiator.Username,
852-
Job: convertProvisionerJob(job),
858+
Job: apiJob,
853859
Deadline: codersdk.NewNullTime(build.Deadline, !build.Deadline.IsZero()),
854860
Reason: codersdk.BuildReason(build.Reason),
855861
Resources: apiResources,
862+
Status: convertWorkspaceStatus(apiJob.Status, transition),
856863
}, nil
857864
}
858865

@@ -898,3 +905,37 @@ func convertWorkspaceResource(resource database.WorkspaceResource, agents []code
898905
Metadata: convertedMetadata,
899906
}
900907
}
908+
909+
func convertWorkspaceStatus(jobStatus codersdk.ProvisionerJobStatus, transition codersdk.WorkspaceTransition) codersdk.WorkspaceStatus {
910+
switch jobStatus {
911+
case codersdk.ProvisionerJobPending:
912+
return codersdk.WorkspaceStatusPending
913+
case codersdk.ProvisionerJobRunning:
914+
switch transition {
915+
case codersdk.WorkspaceTransitionStart:
916+
return codersdk.WorkspaceStatusStarting
917+
case codersdk.WorkspaceTransitionStop:
918+
return codersdk.WorkspaceStatusStopping
919+
case codersdk.WorkspaceTransitionDelete:
920+
return codersdk.WorkspaceStatusDeleting
921+
}
922+
case codersdk.ProvisionerJobSucceeded:
923+
switch transition {
924+
case codersdk.WorkspaceTransitionStart:
925+
return codersdk.WorkspaceStatusRunning
926+
case codersdk.WorkspaceTransitionStop:
927+
return codersdk.WorkspaceStatusStopped
928+
case codersdk.WorkspaceTransitionDelete:
929+
return codersdk.WorkspaceStatusDeleted
930+
}
931+
case codersdk.ProvisionerJobCanceling:
932+
return codersdk.WorkspaceStatusCanceling
933+
case codersdk.ProvisionerJobCanceled:
934+
return codersdk.WorkspaceStatusCanceled
935+
case codersdk.ProvisionerJobFailed:
936+
return codersdk.WorkspaceStatusFailed
937+
}
938+
939+
// return error status since we should never get here
940+
return codersdk.WorkspaceStatusFailed
941+
}

coderd/workspacebuilds_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,50 @@ func TestWorkspaceBuildState(t *testing.T) {
485485
require.NoError(t, err)
486486
require.Equal(t, wantState, gotState)
487487
}
488+
489+
func TestWorkspaceBuildStatus(t *testing.T) {
490+
t.Parallel()
491+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
492+
defer cancel()
493+
client, closeDaemon, api := coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
494+
user := coderdtest.CreateFirstUser(t, client)
495+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
496+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
497+
closeDaemon.Close()
498+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
499+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
500+
501+
// initial returned state is "pending"
502+
require.EqualValues(t, codersdk.WorkspaceStatusPending, workspace.LatestBuild.Status)
503+
504+
closeDaemon = coderdtest.NewProvisionerDaemon(t, api)
505+
// after successful build is "running"
506+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
507+
workspace, err := client.Workspace(ctx, workspace.ID)
508+
require.NoError(t, err)
509+
require.EqualValues(t, codersdk.WorkspaceStatusRunning, workspace.LatestBuild.Status)
510+
511+
// after successful stop is "stopped"
512+
build := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
513+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
514+
workspace, err = client.Workspace(ctx, workspace.ID)
515+
require.NoError(t, err)
516+
require.EqualValues(t, codersdk.WorkspaceStatusStopped, workspace.LatestBuild.Status)
517+
518+
_ = closeDaemon.Close()
519+
// after successful cancel is "canceled"
520+
build = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart)
521+
err = client.CancelWorkspaceBuild(ctx, build.ID)
522+
require.NoError(t, err)
523+
workspace, err = client.Workspace(ctx, workspace.ID)
524+
require.NoError(t, err)
525+
require.EqualValues(t, codersdk.WorkspaceStatusCanceled, workspace.LatestBuild.Status)
526+
527+
_ = coderdtest.NewProvisionerDaemon(t, api)
528+
// after successful delete is "deleted"
529+
build = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionDelete)
530+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
531+
workspace, err = client.DeletedWorkspace(ctx, workspace.ID)
532+
require.NoError(t, err)
533+
require.EqualValues(t, codersdk.WorkspaceStatusDeleted, workspace.LatestBuild.Status)
534+
}

coderd/workspaces.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -996,44 +996,9 @@ func convertWorkspace(
996996
AutostartSchedule: autostartSchedule,
997997
TTLMillis: ttlMillis,
998998
LastUsedAt: workspace.LastUsedAt,
999-
Status: convertStatus(workspaceBuild),
1000999
}
10011000
}
10021001

1003-
func convertStatus(build codersdk.WorkspaceBuild) codersdk.WorkspaceStatus {
1004-
switch build.Job.Status {
1005-
case codersdk.ProvisionerJobPending:
1006-
return codersdk.WorkspaceStatusPending
1007-
case codersdk.ProvisionerJobRunning:
1008-
switch build.Transition {
1009-
case codersdk.WorkspaceTransitionStart:
1010-
return codersdk.WorkspaceStatusStarting
1011-
case codersdk.WorkspaceTransitionStop:
1012-
return codersdk.WorkspaceStatusStopping
1013-
case codersdk.WorkspaceTransitionDelete:
1014-
return codersdk.WorkspaceStatusDeleting
1015-
}
1016-
case codersdk.ProvisionerJobSucceeded:
1017-
switch build.Transition {
1018-
case codersdk.WorkspaceTransitionStart:
1019-
return codersdk.WorkspaceStatusRunning
1020-
case codersdk.WorkspaceTransitionStop:
1021-
return codersdk.WorkspaceStatusStopped
1022-
case codersdk.WorkspaceTransitionDelete:
1023-
return codersdk.WorkspaceStatusDeleted
1024-
}
1025-
case codersdk.ProvisionerJobCanceling:
1026-
return codersdk.WorkspaceStatusCanceling
1027-
case codersdk.ProvisionerJobCanceled:
1028-
return codersdk.WorkspaceStatusCanceled
1029-
case codersdk.ProvisionerJobFailed:
1030-
return codersdk.WorkspaceStatusFailed
1031-
}
1032-
1033-
// return error status since we should never get here
1034-
return codersdk.WorkspaceStatusFailed
1035-
}
1036-
10371002
func convertWorkspaceTTLMillis(i sql.NullInt64) *int64 {
10381003
if !i.Valid {
10391004
return nil

coderd/workspaces_test.go

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,52 +1256,6 @@ func TestWorkspaceWatcher(t *testing.T) {
12561256
require.EqualValues(t, codersdk.Workspace{}, <-wc)
12571257
}
12581258

1259-
func TestWorkspaceStatus(t *testing.T) {
1260-
t.Parallel()
1261-
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1262-
defer cancel()
1263-
var (
1264-
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
1265-
user = coderdtest.CreateFirstUser(t, client)
1266-
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
1267-
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
1268-
template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
1269-
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
1270-
)
1271-
1272-
// initial returned state is "pending"
1273-
require.EqualValues(t, codersdk.WorkspaceStatusPending, workspace.Status)
1274-
1275-
// after successful build is "running"
1276-
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
1277-
workspace, err := client.Workspace(ctx, workspace.ID)
1278-
require.NoError(t, err)
1279-
require.EqualValues(t, codersdk.WorkspaceStatusRunning, workspace.Status)
1280-
1281-
// after successful stop is "stopped"
1282-
build := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
1283-
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
1284-
workspace, err = client.Workspace(ctx, workspace.ID)
1285-
require.NoError(t, err)
1286-
require.EqualValues(t, codersdk.WorkspaceStatusStopped, workspace.Status)
1287-
1288-
// after successful cancel is "canceled"
1289-
build = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart)
1290-
err = client.CancelWorkspaceBuild(ctx, build.ID)
1291-
require.NoError(t, err)
1292-
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
1293-
workspace, err = client.Workspace(ctx, workspace.ID)
1294-
require.NoError(t, err)
1295-
require.EqualValues(t, codersdk.WorkspaceStatusCanceled, workspace.Status)
1296-
1297-
// after successful delete is "deleted"
1298-
build = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionDelete)
1299-
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
1300-
workspace, err = client.DeletedWorkspace(ctx, workspace.ID)
1301-
require.NoError(t, err)
1302-
require.EqualValues(t, codersdk.WorkspaceStatusDeleted, workspace.Status)
1303-
}
1304-
13051259
func mustLocation(t *testing.T, location string) *time.Location {
13061260
t.Helper()
13071261
loc, err := time.LoadLocation(location)

codersdk/workspacebuilds.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,21 @@ const (
1919
WorkspaceTransitionDelete WorkspaceTransition = "delete"
2020
)
2121

22+
type WorkspaceStatus string
23+
24+
const (
25+
WorkspaceStatusPending WorkspaceStatus = "pending"
26+
WorkspaceStatusStarting WorkspaceStatus = "starting"
27+
WorkspaceStatusRunning WorkspaceStatus = "running"
28+
WorkspaceStatusStopping WorkspaceStatus = "stopping"
29+
WorkspaceStatusStopped WorkspaceStatus = "stopped"
30+
WorkspaceStatusFailed WorkspaceStatus = "failed"
31+
WorkspaceStatusCanceling WorkspaceStatus = "canceling"
32+
WorkspaceStatusCanceled WorkspaceStatus = "canceled"
33+
WorkspaceStatusDeleting WorkspaceStatus = "deleting"
34+
WorkspaceStatusDeleted WorkspaceStatus = "deleted"
35+
)
36+
2237
type BuildReason string
2338

2439
const (
@@ -52,6 +67,7 @@ type WorkspaceBuild struct {
5267
Reason BuildReason `db:"reason" json:"reason"`
5368
Resources []WorkspaceResource `json:"resources"`
5469
Deadline NullTime `json:"deadline,omitempty"`
70+
Status WorkspaceStatus `json:"status"`
5571
}
5672

5773
// WorkspaceBuild returns a single workspace build for a workspace.

0 commit comments

Comments
 (0)