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

Commitbe1b513

Browse files
committed
feat: add cli command scaletest dynamic-parameters
1 parent8cfa2c4 commitbe1b513

File tree

9 files changed

+481
-7
lines changed

9 files changed

+481
-7
lines changed

‎cli/exp_scaletest.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func (r *RootCmd) scaletestCmd() *serpent.Command {
6060
r.scaletestCreateWorkspaces(),
6161
r.scaletestWorkspaceUpdates(),
6262
r.scaletestWorkspaceTraffic(),
63+
r.scaletestDynamicParameters(),
6364
},
6465
}
6566

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//go:build !slim
2+
3+
package cli
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/prometheus/client_golang/prometheus"
9+
"golang.org/x/xerrors"
10+
11+
"cdr.dev/slog"
12+
"cdr.dev/slog/sloggers/sloghuman"
13+
14+
"github.com/coder/coder/v2/scaletest/dynamicparameters"
15+
"github.com/coder/coder/v2/scaletest/harness"
16+
"github.com/coder/serpent"
17+
)
18+
19+
const (
20+
dynamicParametersTestName="dynamic-parameters"
21+
)
22+
23+
func (r*RootCmd)scaletestDynamicParameters()*serpent.Command {
24+
vartemplateNamestring
25+
varnumEvalsint64
26+
orgContext:=NewOrganizationContext()
27+
output:=&scaletestOutputFlags{}
28+
29+
cmd:=&serpent.Command{
30+
Use:"dynamic-parameters",
31+
Short:"Generates load on the Coder server evaluating dynamic parameters",
32+
Long:`It is recommended that all rate limits are disabled on the server before running this scaletest. This test generates many login events which will be rate limited against the (most likely single) IP.`,
33+
Handler:func(inv*serpent.Invocation)error {
34+
ctx:=inv.Context()
35+
36+
outputs,err:=output.parse()
37+
iferr!=nil {
38+
returnxerrors.Errorf("could not parse --output flags")
39+
}
40+
41+
client,err:=r.InitClient(inv)
42+
iferr!=nil {
43+
returnerr
44+
}
45+
iftemplateName=="" {
46+
returnxerrors.Errorf("template cannot be empty")
47+
}
48+
49+
org,err:=orgContext.Selected(inv,client)
50+
iferr!=nil {
51+
returnerr
52+
}
53+
54+
logger:=slog.Make(sloghuman.Sink(inv.Stdout)).Leveled(slog.LevelDebug)
55+
partitions,err:=dynamicparameters.SetupPartitions(ctx,client,org.ID,templateName,numEvals,logger)
56+
iferr!=nil {
57+
returnxerrors.Errorf("setup dynamic parameters partitions: %w",err)
58+
}
59+
60+
th:=harness.NewTestHarness(harness.ConcurrentExecutionStrategy{}, harness.ConcurrentExecutionStrategy{})
61+
reg:=prometheus.NewRegistry()
62+
metrics:=dynamicparameters.NewMetrics(reg,"concurrent_evaluations")
63+
64+
fori,part:=rangepartitions {
65+
forj:=0;j<part.ConcurrentEvaluations;j++ {
66+
cfg:= dynamicparameters.Config{
67+
TemplateVersion:part.TemplateVersion.ID,
68+
Metrics:metrics,
69+
MetricLabelValues: []string{fmt.Sprintf("%d",part.ConcurrentEvaluations)},
70+
}
71+
runner:=dynamicparameters.NewRunner(client,cfg)
72+
th.AddRun(dynamicParametersTestName,fmt.Sprintf("%d/%d",j,i),runner)
73+
}
74+
}
75+
76+
err=th.Run(ctx)
77+
iferr!=nil {
78+
returnxerrors.Errorf("run test harness: %w",err)
79+
}
80+
81+
res:=th.Results()
82+
for_,o:=rangeoutputs {
83+
err=o.write(res,inv.Stdout)
84+
iferr!=nil {
85+
returnxerrors.Errorf("write output %q to %q: %w",o.format,o.path,err)
86+
}
87+
}
88+
89+
returnnil
90+
},
91+
}
92+
93+
cmd.Options= serpent.OptionSet{
94+
{
95+
Flag:"template",
96+
Description:"Name of the template to use. If it does not exist, it will be created.",
97+
Default:"scaletest-dynamic-parameters",
98+
Value:serpent.StringOf(&templateName),
99+
},
100+
{
101+
Flag:"concurrent-evaluations",
102+
Description:"Number of concurrent dynamic parameter evaluations to perform.",
103+
Default:"100",
104+
Value:serpent.Int64Of(&numEvals),
105+
},
106+
}
107+
orgContext.AttachOptions(cmd)
108+
output.attach(&cmd.Options)
109+
returncmd
110+
}

‎codersdk/client.go‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,16 @@ func (e *Error) Error() string {
519519
returnbuilder.String()
520520
}
521521

522+
// NewTestError is a helper function to create a Error, setting the internal fields. It's generally only useful for
523+
// testing.
524+
funcNewTestError(statusCodeint,methodstring,ustring)*Error {
525+
return&Error{
526+
statusCode:statusCode,
527+
method:method,
528+
url:u,
529+
}
530+
}
531+
522532
typecloseFuncfunc()error
523533

524534
func (ccloseFunc)Close()error {

‎scaletest/dynamicparameters/config.go‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import "github.com/google/uuid"
44

55
typeConfigstruct {
66
TemplateVersion uuid.UUID`json:"template_version"`
7-
SessionTokenstring`json:"session_token"`
87
Metrics*Metrics`json:"-"`
98
MetricLabelValues []string`json:"metric_label_values"`
109
}

‎scaletest/dynamicparameters/run.go‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,8 @@ type Runner struct {
2222
var_ harness.Runnable=&Runner{}
2323

2424
funcNewRunner(client*codersdk.Client,cfgConfig)*Runner {
25-
clone:=codersdk.New(client.URL)
26-
clone.HTTPClient=client.HTTPClient
27-
clone.SetLogger(client.Logger())
28-
clone.SetSessionToken(cfg.SessionToken)
2925
return&Runner{
30-
client:clone,
26+
client:client,
3127
cfg:cfg,
3228
}
3329
}

‎scaletest/dynamicparameters/run_test.go‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ func TestRun(t *testing.T) {
3737
reg:=prometheus.NewRegistry()
3838
cfg:= dynamicparameters.Config{
3939
TemplateVersion:version.ID,
40-
SessionToken:userClient.SessionToken(),
4140
Metrics:dynamicparameters.NewMetrics(reg,"template","test_label_name"),
4241
MetricLabelValues: []string{template.Name,"test_label_value"},
4342
}

‎scaletest/dynamicparameters/template.go‎

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
package dynamicparameters
22

33
import (
4+
"archive/tar"
5+
"bytes"
6+
"context"
47
_"embed"
58
"encoding/json"
9+
"io"
10+
"path/filepath"
11+
"slices"
612
"strings"
713
"text/template"
814

15+
"github.com/google/uuid"
16+
"golang.org/x/xerrors"
17+
18+
"cdr.dev/slog"
19+
"github.com/coder/coder/v2/codersdk"
920
"github.com/coder/coder/v2/cryptorand"
1021
)
1122

@@ -72,3 +83,187 @@ func GetModuleFiles() map[string][]byte {
7283
".terraform/modules/modules.json":modulesJSONBytes,
7384
}
7485
}
86+
87+
funccreateTarFromFiles(filesmap[string][]byte) ([]byte,error) {
88+
buf:=new(bytes.Buffer)
89+
writer:=tar.NewWriter(buf)
90+
dirs:= []string{}
91+
forname,content:=rangefiles {
92+
// We need to add directories before any files that use them. But, we only need to do this
93+
// once.
94+
dir:=filepath.Dir(name)
95+
ifdir!="."&&!slices.Contains(dirs,dir) {
96+
dirs=append(dirs,dir)
97+
err:=writer.WriteHeader(&tar.Header{
98+
Name:dir,
99+
Mode:0o755,
100+
Typeflag:tar.TypeDir,
101+
})
102+
iferr!=nil {
103+
returnnil,err
104+
}
105+
}
106+
107+
err:=writer.WriteHeader(&tar.Header{
108+
Name:name,
109+
Size:int64(len(content)),
110+
Mode:0o644,
111+
})
112+
iferr!=nil {
113+
returnnil,err
114+
}
115+
116+
_,err=writer.Write(content)
117+
iferr!=nil {
118+
returnnil,err
119+
}
120+
}
121+
// `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball.
122+
err:=writer.Close()
123+
iferr!=nil {
124+
returnnil,err
125+
}
126+
returnbuf.Bytes(),nil
127+
}
128+
129+
funcTemplateTarData() ([]byte,error) {
130+
mainTF,err:=TemplateContent()
131+
iferr!=nil {
132+
returnnil,xerrors.Errorf("failed to generate main.tf: %w",err)
133+
}
134+
moduleFiles:=GetModuleFiles()
135+
136+
files:=map[string][]byte{
137+
"main.tf": []byte(mainTF),
138+
}
139+
fork,v:=rangemoduleFiles {
140+
files[k]=v
141+
}
142+
tarData,err:=createTarFromFiles(files)
143+
iferr!=nil {
144+
returnnil,xerrors.Errorf("failed to create tarball: %w",err)
145+
}
146+
147+
returntarData,nil
148+
}
149+
150+
typePartitionstruct {
151+
TemplateVersion codersdk.TemplateVersion
152+
ConcurrentEvaluationsint
153+
}
154+
155+
typeSDKForDynamicParametersSetupinterface {
156+
TemplateByName(ctx context.Context,orgID uuid.UUID,templateNamestring) (codersdk.Template,error)
157+
CreateTemplate(ctx context.Context,orgID uuid.UUID,createReq codersdk.CreateTemplateRequest) (codersdk.Template,error)
158+
CreateTemplateVersion(ctx context.Context,orgID uuid.UUID,createReq codersdk.CreateTemplateVersionRequest) (codersdk.TemplateVersion,error)
159+
Upload(ctx context.Context,contentTypestring,reader io.Reader) (codersdk.UploadResponse,error)
160+
}
161+
162+
funcSetupPartitions(
163+
ctx context.Context,clientSDKForDynamicParametersSetup,
164+
orgID uuid.UUID,templateNamestring,numEvalsint64,
165+
logger slog.Logger,
166+
) ([]Partition,error) {
167+
var (
168+
errerror
169+
coderError*codersdk.Error
170+
templ codersdk.Template
171+
tempVersion codersdk.TemplateVersion
172+
)
173+
templ,err=client.TemplateByName(ctx,orgID,templateName)
174+
ifxerrors.As(err,&coderError)&&coderError.StatusCode()==404 {
175+
tempVersion,err=createTemplateVersion(ctx,client,orgID,uuid.Nil)
176+
iferr!=nil {
177+
returnnil,xerrors.Errorf("failed to create template version: %w",err)
178+
}
179+
logger.Info(ctx,"created template version",slog.F("version_id",tempVersion.ID))
180+
createReq:= codersdk.CreateTemplateRequest{
181+
Name:templateName,
182+
DisplayName:"Scaletest Dynamic Parameters",
183+
Description:"`coder exp scaletest dynamic parameters test` template",
184+
VersionID:tempVersion.ID,
185+
}
186+
templ,err=client.CreateTemplate(ctx,orgID,createReq)
187+
iferr!=nil {
188+
returnnil,xerrors.Errorf("failed to create template: %w",err)
189+
}
190+
logger.Info(ctx,"created template",slog.F("template_id",templ.ID),slog.F("name",templateName))
191+
}elseiferr!=nil {
192+
returnnil,xerrors.Errorf("failed to get template: %w",err)
193+
}
194+
195+
// Partition the number into a list decreasing by half each time
196+
evalParts:=partitionEvaluations(int(numEvals))
197+
logger.Info(ctx,"partitioned evaluations",slog.F("num_evals",numEvals),slog.F("eval_parts",evalParts))
198+
199+
// If tempVersion is not empty (i.e. we created it above), use it as the first version.
200+
partitions:=make([]Partition,0,len(evalParts))
201+
iftempVersion.ID!=uuid.Nil {
202+
partitions=append(partitions,Partition{
203+
TemplateVersion:tempVersion,
204+
ConcurrentEvaluations:evalParts[0],
205+
})
206+
evalParts=evalParts[1:]
207+
}
208+
209+
for_,num:=rangeevalParts {
210+
version,err:=createTemplateVersion(ctx,client,orgID,templ.ID)
211+
iferr!=nil {
212+
returnnil,xerrors.Errorf("failed to create template version: %w",err)
213+
}
214+
partitions=append(partitions,Partition{
215+
TemplateVersion:version,
216+
ConcurrentEvaluations:num,
217+
})
218+
logger.Info(ctx,"created template version",slog.F("version_id",version.ID))
219+
}
220+
returnpartitions,nil
221+
}
222+
223+
// createTemplateVersion generates the template tarball, uploads it, and creates a template version. Returns the version and any error.
224+
// If templateID is not uuid.Nil, it will be used to update an existing template instead of creating a new one.
225+
funccreateTemplateVersion(ctx context.Context,clientSDKForDynamicParametersSetup,orgID uuid.UUID,templateID uuid.UUID) (codersdk.TemplateVersion,error) {
226+
tarData,err:=TemplateTarData()
227+
iferr!=nil {
228+
return codersdk.TemplateVersion{},xerrors.Errorf("failed to create template tarball: %w",err)
229+
}
230+
231+
// Upload tarball
232+
uploadResp,err:=client.Upload(ctx,codersdk.ContentTypeTar,bytes.NewReader(tarData))
233+
iferr!=nil {
234+
return codersdk.TemplateVersion{},xerrors.Errorf("failed to upload template tar: %w",err)
235+
}
236+
237+
// Create template version
238+
versionReq:= codersdk.CreateTemplateVersionRequest{
239+
TemplateID:templateID,
240+
FileID:uploadResp.ID,
241+
Message:"Initial version for scaletest dynamic parameters",
242+
StorageMethod:codersdk.ProvisionerStorageMethodFile,
243+
Provisioner:codersdk.ProvisionerTypeTerraform,
244+
}
245+
version,err:=client.CreateTemplateVersion(ctx,orgID,versionReq)
246+
iferr!=nil {
247+
return codersdk.TemplateVersion{},xerrors.Errorf("failed to create template version: %w",err)
248+
}
249+
returnversion,nil
250+
}
251+
252+
// partitionEvaluations partitions a number into a list decreasing by half each time.
253+
funcpartitionEvaluations(totalint) []int {
254+
varparts []int
255+
remaining:=total
256+
forremaining>0 {
257+
next:=remaining/2
258+
// round up
259+
ifnext*2!=remaining {
260+
next++
261+
}
262+
ifnext>remaining {
263+
next=remaining
264+
}
265+
parts=append(parts,next)
266+
remaining-=next
267+
}
268+
returnparts
269+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp