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

Skip to content

Commit b63d7e8

Browse files
committed
feat: add workspacebuild load test runner
1 parent 02ec880 commit b63d7e8

File tree

2 files changed

+418
-0
lines changed

2 files changed

+418
-0
lines changed

loadtest/workspacebuild/run.go

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package workspacebuild
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"time"
8+
9+
"github.com/google/uuid"
10+
"golang.org/x/xerrors"
11+
12+
"github.com/coder/coder/codersdk"
13+
"github.com/coder/coder/cryptorand"
14+
"github.com/coder/coder/loadtest/harness"
15+
)
16+
17+
type Runner struct {
18+
client *codersdk.Client
19+
orgID uuid.UUID
20+
userID string
21+
req codersdk.CreateWorkspaceRequest
22+
23+
workspaceID uuid.UUID
24+
}
25+
26+
var _ harness.Runnable = &Runner{}
27+
var _ harness.Cleanable = &Runner{}
28+
29+
// NewRunner creates a new workspace build loadtest Runner. The provided request
30+
// will be used verbatim, but the name will be set to a random value if unset.
31+
//
32+
// The Cleanup method will delete the workspace if it was successfully created.
33+
func NewRunner(client *codersdk.Client, orgID uuid.UUID, userID string, req codersdk.CreateWorkspaceRequest) *Runner {
34+
return &Runner{
35+
client: client,
36+
orgID: orgID,
37+
userID: userID,
38+
req: req,
39+
}
40+
}
41+
42+
// Run implements Runnable.
43+
func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error {
44+
req := r.req
45+
if req.Name == "" {
46+
randName, err := cryptorand.HexString(8)
47+
if err != nil {
48+
return xerrors.Errorf("generate random name for workspace: %w", err)
49+
}
50+
req.Name = "test-" + randName
51+
}
52+
53+
after := time.Now()
54+
workspace, err := r.client.CreateWorkspace(ctx, r.orgID, r.userID, req)
55+
if err != nil {
56+
return xerrors.Errorf("create workspace: %w", err)
57+
}
58+
r.workspaceID = workspace.ID
59+
60+
err = waitForBuild(ctx, logs, r.client, workspace.LatestBuild.ID, after)
61+
if err != nil {
62+
return xerrors.Errorf("wait for build: %w", err)
63+
}
64+
65+
_, _ = fmt.Fprintln(logs, "")
66+
err = waitForAgents(ctx, logs, r.client, workspace.ID)
67+
if err != nil {
68+
return xerrors.Errorf("wait for agent: %w", err)
69+
}
70+
71+
return nil
72+
}
73+
74+
// Cleanup implements Cleanable.
75+
func (r *Runner) Cleanup(ctx context.Context, _ string) error {
76+
if r.workspaceID == uuid.Nil {
77+
return nil
78+
}
79+
80+
after := time.Now()
81+
build, err := r.client.CreateWorkspaceBuild(ctx, r.workspaceID, codersdk.CreateWorkspaceBuildRequest{
82+
Transition: codersdk.WorkspaceTransitionDelete,
83+
})
84+
if err != nil {
85+
return xerrors.Errorf("delete workspace: %w", err)
86+
}
87+
88+
// TODO: capture these logs
89+
logs := io.Discard
90+
err = waitForBuild(ctx, logs, r.client, build.ID, after)
91+
if err != nil {
92+
return xerrors.Errorf("wait for build: %w", err)
93+
}
94+
95+
return nil
96+
}
97+
98+
func waitForBuild(ctx context.Context, w io.Writer, client *codersdk.Client, buildID uuid.UUID, after time.Time) error {
99+
_, _ = fmt.Fprint(w, "Build is currently queued...")
100+
101+
// Wait for build to start.
102+
for {
103+
build, err := client.WorkspaceBuild(ctx, buildID)
104+
if err != nil {
105+
return xerrors.Errorf("fetch build: %w", err)
106+
}
107+
108+
if build.Job.Status != codersdk.ProvisionerJobPending {
109+
break
110+
}
111+
112+
_, _ = fmt.Fprint(w, ".")
113+
time.Sleep(500 * time.Millisecond)
114+
}
115+
116+
_, _ = fmt.Fprintln(w, "\nBuild started! Streaming logs below:")
117+
118+
logs, closer, err := client.WorkspaceBuildLogsAfter(ctx, buildID, after)
119+
if err != nil {
120+
return xerrors.Errorf("start streaming build logs: %w", err)
121+
}
122+
defer closer.Close()
123+
124+
currentStage := ""
125+
for {
126+
select {
127+
case <-ctx.Done():
128+
return ctx.Err()
129+
case log, ok := <-logs:
130+
if !ok {
131+
build, err := client.WorkspaceBuild(ctx, buildID)
132+
if err != nil {
133+
return xerrors.Errorf("fetch build: %w", err)
134+
}
135+
136+
_, _ = fmt.Fprintln(w, "")
137+
switch build.Job.Status {
138+
case codersdk.ProvisionerJobSucceeded:
139+
_, _ = fmt.Fprintln(w, "\nBuild succeeded!")
140+
return nil
141+
case codersdk.ProvisionerJobFailed:
142+
_, _ = fmt.Fprintf(w, "\nBuild failed with error %q.\nSee logs above for more details.\n", build.Job.Error)
143+
return xerrors.Errorf("build failed with status %q: %s", build.Job.Status, build.Job.Error)
144+
case codersdk.ProvisionerJobCanceled:
145+
_, _ = fmt.Fprintln(w, "\nBuild canceled.")
146+
return xerrors.New("build canceled")
147+
default:
148+
_, _ = fmt.Fprintf(w, "\nLogs disconnected with unexpected job status %q and error %q.\n", build.Job.Status, build.Job.Error)
149+
return xerrors.Errorf("logs disconnected with unexpected job status %q and error %q", build.Job.Status, build.Job.Error)
150+
}
151+
}
152+
153+
if log.Stage != currentStage {
154+
currentStage = log.Stage
155+
_, _ = fmt.Fprintf(w, "\n%s\n", currentStage)
156+
}
157+
158+
level := "unknown"
159+
if log.Level != "" {
160+
level = string(log.Level)
161+
}
162+
_, _ = fmt.Fprintf(w, "\t%s:\t%s\n", level, log.Output)
163+
}
164+
}
165+
}
166+
167+
func waitForAgents(ctx context.Context, w io.Writer, client *codersdk.Client, workspaceID uuid.UUID) error {
168+
_, _ = fmt.Fprint(w, "Waiting for agents to connect...\n\n")
169+
170+
for {
171+
select {
172+
case <-ctx.Done():
173+
return ctx.Err()
174+
default:
175+
}
176+
177+
workspace, err := client.Workspace(ctx, workspaceID)
178+
if err != nil {
179+
return xerrors.Errorf("fetch workspace: %w", err)
180+
}
181+
182+
ok := true
183+
for _, res := range workspace.LatestBuild.Resources {
184+
for _, agent := range res.Agents {
185+
if agent.Status != codersdk.WorkspaceAgentConnected {
186+
ok = false
187+
}
188+
189+
_, _ = fmt.Fprintf(w, "\tAgent %q is %s\n", agent.Name, agent.Status)
190+
}
191+
}
192+
if ok {
193+
break
194+
}
195+
196+
_, _ = fmt.Fprintln(w, "")
197+
time.Sleep(1 * time.Second)
198+
}
199+
200+
_, _ = fmt.Fprint(w, "\nAgents connected!\n\n")
201+
return nil
202+
}

0 commit comments

Comments
 (0)