Skip to content

Commit ef9d84c

Browse files
authored
fix(scaletest): cleanup: attempt to cancel in-progress jobs (#9080)
This change modifies the cleanup behaviour to make a best-effort attempt to cancel the in-progress scaletest workspace build jobs before deleting them.
1 parent 72575cc commit ef9d84c

File tree

2 files changed

+136
-1
lines changed

2 files changed

+136
-1
lines changed

scaletest/createworkspaces/run_test.go

+112
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/coder/coder/agent"
1717
"github.com/coder/coder/coderd/coderdtest"
1818
"github.com/coder/coder/coderd/httpapi"
19+
"github.com/coder/coder/coderd/util/ptr"
1920
"github.com/coder/coder/codersdk"
2021
"github.com/coder/coder/codersdk/agentsdk"
2122
"github.com/coder/coder/provisioner/echo"
@@ -156,6 +157,117 @@ func Test_Runner(t *testing.T) {
156157
require.Len(t, workspaces.Workspaces, 0)
157158
})
158159

160+
t.Run("CleanupPendingBuild", func(t *testing.T) {
161+
t.Parallel()
162+
163+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
164+
defer cancel()
165+
166+
client := coderdtest.New(t, &coderdtest.Options{
167+
IncludeProvisionerDaemon: true,
168+
})
169+
user := coderdtest.CreateFirstUser(t, client)
170+
171+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
172+
Parse: echo.ParseComplete,
173+
ProvisionPlan: echo.ProvisionComplete,
174+
ProvisionApply: []*proto.Provision_Response{
175+
{
176+
Type: &proto.Provision_Response_Log{Log: &proto.Log{}},
177+
},
178+
},
179+
})
180+
181+
version = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
182+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) {
183+
request.AllowUserCancelWorkspaceJobs = ptr.Ref(true)
184+
})
185+
186+
const (
187+
username = "scaletest-user"
188+
email = "scaletest@test.coder.com"
189+
)
190+
runner := createworkspaces.NewRunner(client, createworkspaces.Config{
191+
User: createworkspaces.UserConfig{
192+
OrganizationID: user.OrganizationID,
193+
Username: username,
194+
Email: email,
195+
},
196+
Workspace: workspacebuild.Config{
197+
OrganizationID: user.OrganizationID,
198+
Request: codersdk.CreateWorkspaceRequest{
199+
TemplateID: template.ID,
200+
},
201+
},
202+
})
203+
204+
cancelCtx, cancelFunc := context.WithCancel(ctx)
205+
done := make(chan struct{})
206+
logs := bytes.NewBuffer(nil)
207+
go func() {
208+
err := runner.Run(cancelCtx, "1", logs)
209+
logsStr := logs.String()
210+
t.Log("Runner logs:\n\n" + logsStr)
211+
require.ErrorIs(t, err, context.Canceled)
212+
close(done)
213+
}()
214+
215+
require.Eventually(t, func() bool {
216+
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
217+
if err != nil {
218+
return false
219+
}
220+
221+
return len(workspaces.Workspaces) > 0
222+
}, testutil.WaitShort, testutil.IntervalFast)
223+
224+
cancelFunc()
225+
<-done
226+
227+
// When we run the cleanup, it should be canceled
228+
cancelCtx, cancelFunc = context.WithCancel(ctx)
229+
done = make(chan struct{})
230+
go func() {
231+
// This will return an error as the "delete" operation will never complete.
232+
_ = runner.Cleanup(cancelCtx, "1")
233+
close(done)
234+
}()
235+
236+
// Ensure the job has been marked as deleted
237+
require.Eventually(t, func() bool {
238+
workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{})
239+
if err != nil {
240+
return false
241+
}
242+
243+
if len(workspaces.Workspaces) == 0 {
244+
return false
245+
}
246+
247+
// There should be two builds
248+
builds, err := client.WorkspaceBuilds(ctx, codersdk.WorkspaceBuildsRequest{
249+
WorkspaceID: workspaces.Workspaces[0].ID,
250+
})
251+
if err != nil {
252+
return false
253+
}
254+
for _, build := range builds {
255+
// One of the builds should be for creating the workspace,
256+
if build.Transition != codersdk.WorkspaceTransitionStart {
257+
continue
258+
}
259+
260+
// And it should be either canceled or cancelling
261+
if build.Job.Status == codersdk.ProvisionerJobCanceled || build.Job.Status == codersdk.ProvisionerJobCanceling {
262+
return true
263+
}
264+
}
265+
return false
266+
}, testutil.WaitShort, testutil.IntervalFast)
267+
cancelFunc()
268+
<-done
269+
})
270+
159271
t.Run("NoCleanup", func(t *testing.T) {
160272
t.Parallel()
161273

scaletest/workspacebuild/run.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,30 @@ func (r *CleanupRunner) Run(ctx context.Context, _ string, logs io.Writer) error
112112
ctx, span := tracing.StartSpan(ctx)
113113
defer span.End()
114114

115-
build, err := r.client.CreateWorkspaceBuild(ctx, r.workspaceID, codersdk.CreateWorkspaceBuildRequest{
115+
logs = loadtestutil.NewSyncWriter(logs)
116+
logger := slog.Make(sloghuman.Sink(logs)).Leveled(slog.LevelDebug)
117+
r.client.SetLogger(logger)
118+
r.client.SetLogBodies(true)
119+
120+
ws, err := r.client.Workspace(ctx, r.workspaceID)
121+
if err != nil {
122+
return err
123+
}
124+
125+
build, err := r.client.WorkspaceBuild(ctx, ws.LatestBuild.ID)
126+
if err == nil && build.Job.Status.Active() {
127+
// mark the build as canceled
128+
if err = r.client.CancelWorkspaceBuild(ctx, build.ID); err == nil {
129+
// Wait for the job to cancel before we delete it
130+
_ = waitForBuild(ctx, logs, r.client, build.ID) // it will return a "build canceled" error
131+
} else {
132+
logger.Warn(ctx, "failed to cancel workspace build, attempting to delete anyway", slog.Error(err))
133+
}
134+
} else {
135+
logger.Warn(ctx, "unable to lookup latest workspace build, attempting to delete anyway", slog.Error(err))
136+
}
137+
138+
build, err = r.client.CreateWorkspaceBuild(ctx, r.workspaceID, codersdk.CreateWorkspaceBuildRequest{
116139
Transition: codersdk.WorkspaceTransitionDelete,
117140
})
118141
if err != nil {

0 commit comments

Comments
 (0)