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

Commitfff0900

Browse files
committed
feat(scaletest): add runner for prebuilds
1 parent9c47733 commitfff0900

File tree

7 files changed

+776
-46
lines changed

7 files changed

+776
-46
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package prebuilds_test
2+
3+
import (
4+
"io"
5+
"strconv"
6+
"sync"
7+
"testing"
8+
9+
"github.com/prometheus/client_golang/prometheus"
10+
"github.com/stretchr/testify/require"
11+
"golang.org/x/sync/errgroup"
12+
13+
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
15+
"github.com/coder/coder/v2/enterprise/coderd/license"
16+
"github.com/coder/coder/v2/scaletest/prebuilds"
17+
"github.com/coder/coder/v2/testutil"
18+
"github.com/coder/quartz"
19+
)
20+
21+
funcTestRun(t*testing.T) {
22+
t.Parallel()
23+
24+
t.Skip("This test takes several minutes to run, and is intended as a manual regression test")
25+
26+
ctx:=testutil.Context(t,testutil.WaitSuperLong*3)
27+
28+
client,user:=coderdenttest.New(t,&coderdenttest.Options{
29+
LicenseOptions:&coderdenttest.LicenseOptions{
30+
Features: license.Features{
31+
codersdk.FeatureWorkspacePrebuilds:1,
32+
codersdk.FeatureExternalProvisionerDaemons:1,
33+
},
34+
},
35+
})
36+
37+
// This is a real Terraform provisioner
38+
_=coderdenttest.NewExternalProvisionerDaemonTerraform(t,client,user.OrganizationID,nil)
39+
40+
numTemplates:=2
41+
numPresets:=1
42+
numPresetPrebuilds:=1
43+
44+
//nolint:gocritic // It's fine to use the owner user to pause prebuilds
45+
err:=client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{
46+
ReconciliationPaused:true,
47+
})
48+
require.NoError(t,err)
49+
50+
setupBarrier:=new(sync.WaitGroup)
51+
setupBarrier.Add(numTemplates)
52+
deletionBarrier:=new(sync.WaitGroup)
53+
deletionBarrier.Add(numTemplates)
54+
55+
metrics:=prebuilds.NewMetrics(prometheus.NewRegistry())
56+
57+
eg,runCtx:=errgroup.WithContext(ctx)
58+
59+
runners:=make([]*prebuilds.Runner,0,numTemplates)
60+
fori:=rangenumTemplates {
61+
cfg:= prebuilds.Config{
62+
OrganizationID:user.OrganizationID,
63+
NumPresets:numPresets,
64+
NumPresetPrebuilds:numPresetPrebuilds,
65+
TemplateVersionJobTimeout:testutil.WaitSuperLong*2,
66+
PrebuildWorkspaceTimeout:testutil.WaitSuperLong*2,
67+
Metrics:metrics,
68+
SetupBarrier:setupBarrier,
69+
DeletionBarrier:deletionBarrier,
70+
Clock:quartz.NewReal(),
71+
}
72+
err:=cfg.Validate()
73+
require.NoError(t,err)
74+
75+
runner:=prebuilds.NewRunner(client,cfg)
76+
runners=append(runners,runner)
77+
eg.Go(func()error {
78+
returnrunner.Run(runCtx,strconv.Itoa(i),io.Discard)
79+
})
80+
}
81+
82+
// Wait for all runners to reach the setup barrier (templates created)
83+
setupBarrier.Wait()
84+
85+
// Resume prebuilds to trigger prebuild creation
86+
err=client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{
87+
ReconciliationPaused:false,
88+
})
89+
require.NoError(t,err)
90+
91+
err=eg.Wait()
92+
require.NoError(t,err)
93+
94+
//nolint:gocritic // Owner user is fine here as we want to view all workspaces
95+
workspaces,err:=client.Workspaces(ctx, codersdk.WorkspaceFilter{})
96+
require.NoError(t,err)
97+
expectedWorkspaces:=numTemplates*numPresets*numPresetPrebuilds
98+
require.Equal(t,workspaces.Count,expectedWorkspaces)
99+
100+
// Now run Cleanup which measures deletion
101+
// First pause prebuilds again
102+
err=client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{
103+
ReconciliationPaused:true,
104+
})
105+
require.NoError(t,err)
106+
107+
cleanupEg,cleanupCtx:=errgroup.WithContext(ctx)
108+
fori,runner:=rangerunners {
109+
cleanupEg.Go(func()error {
110+
returnrunner.Cleanup(cleanupCtx,strconv.Itoa(i),io.Discard)
111+
})
112+
}
113+
114+
// Wait for all runners to reach the deletion barrier (template versions updated to 0 prebuilds)
115+
deletionBarrier.Wait()
116+
117+
// Resume prebuilds to trigger prebuild deletion
118+
err=client.PutPrebuildsSettings(ctx, codersdk.PrebuildsSettings{
119+
ReconciliationPaused:false,
120+
})
121+
require.NoError(t,err)
122+
123+
err=cleanupEg.Wait()
124+
require.NoError(t,err)
125+
126+
// Verify all prebuild workspaces were deleted
127+
workspaces,err=client.Workspaces(ctx, codersdk.WorkspaceFilter{})
128+
require.NoError(t,err)
129+
require.Equal(t,workspaces.Count,0)
130+
131+
for_,runner:=rangerunners {
132+
metrics:=runner.GetMetrics()
133+
134+
require.Contains(t,metrics,prebuilds.PrebuildsTotalLatencyMetric)
135+
require.Contains(t,metrics,prebuilds.PrebuildJobCreationLatencyMetric)
136+
require.Contains(t,metrics,prebuilds.PrebuildJobAcquiredLatencyMetric)
137+
138+
creationLatency,ok:=metrics[prebuilds.PrebuildsTotalLatencyMetric].(int64)
139+
require.True(t,ok)
140+
jobCreationLatency,ok:=metrics[prebuilds.PrebuildJobCreationLatencyMetric].(int64)
141+
require.True(t,ok)
142+
jobAcquiredLatency,ok:=metrics[prebuilds.PrebuildJobAcquiredLatencyMetric].(int64)
143+
require.True(t,ok)
144+
145+
require.Greater(t,creationLatency,int64(0))
146+
require.Greater(t,jobCreationLatency,int64(0))
147+
require.Greater(t,jobAcquiredLatency,int64(0))
148+
149+
require.Contains(t,metrics,prebuilds.PrebuildDeletionTotalLatencyMetric)
150+
require.Contains(t,metrics,prebuilds.PrebuildDeletionJobCreationLatencyMetric)
151+
require.Contains(t,metrics,prebuilds.PrebuildDeletionJobAcquiredLatencyMetric)
152+
153+
deletionLatency,ok:=metrics[prebuilds.PrebuildDeletionTotalLatencyMetric].(int64)
154+
require.True(t,ok)
155+
deletionJobCreationLatency,ok:=metrics[prebuilds.PrebuildDeletionJobCreationLatencyMetric].(int64)
156+
require.True(t,ok)
157+
deletionJobAcquiredLatency,ok:=metrics[prebuilds.PrebuildDeletionJobAcquiredLatencyMetric].(int64)
158+
require.True(t,ok)
159+
160+
require.Greater(t,deletionLatency,int64(0))
161+
require.Greater(t,deletionJobCreationLatency,int64(0))
162+
require.Greater(t,deletionJobAcquiredLatency,int64(0))
163+
}
164+
}

‎scaletest/dynamicparameters/template.go‎

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package dynamicparameters
22

33
import (
4-
"archive/tar"
54
"bytes"
65
"context"
76
_"embed"
87
"encoding/json"
98
"fmt"
109
"io"
11-
"path/filepath"
12-
"slices"
1310
"strings"
1411
"text/template"
1512
"time"
@@ -20,6 +17,7 @@ import (
2017
"cdr.dev/slog"
2118
"github.com/coder/coder/v2/codersdk"
2219
"github.com/coder/coder/v2/cryptorand"
20+
"github.com/coder/coder/v2/scaletest/loadtestutil"
2321
"github.com/coder/quartz"
2422
)
2523

@@ -89,48 +87,6 @@ func GetModuleFiles() map[string][]byte {
8987
}
9088
}
9189

92-
funccreateTarFromFiles(filesmap[string][]byte) ([]byte,error) {
93-
buf:=new(bytes.Buffer)
94-
writer:=tar.NewWriter(buf)
95-
dirs:= []string{}
96-
forname,content:=rangefiles {
97-
// We need to add directories before any files that use them. But, we only need to do this
98-
// once.
99-
dir:=filepath.Dir(name)
100-
ifdir!="."&&!slices.Contains(dirs,dir) {
101-
dirs=append(dirs,dir)
102-
err:=writer.WriteHeader(&tar.Header{
103-
Name:dir,
104-
Mode:0o755,
105-
Typeflag:tar.TypeDir,
106-
})
107-
iferr!=nil {
108-
returnnil,err
109-
}
110-
}
111-
112-
err:=writer.WriteHeader(&tar.Header{
113-
Name:name,
114-
Size:int64(len(content)),
115-
Mode:0o644,
116-
})
117-
iferr!=nil {
118-
returnnil,err
119-
}
120-
121-
_,err=writer.Write(content)
122-
iferr!=nil {
123-
returnnil,err
124-
}
125-
}
126-
// `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball.
127-
err:=writer.Close()
128-
iferr!=nil {
129-
returnnil,err
130-
}
131-
returnbuf.Bytes(),nil
132-
}
133-
13490
funcTemplateTarData() ([]byte,error) {
13591
mainTF,err:=TemplateContent()
13692
iferr!=nil {
@@ -144,7 +100,7 @@ func TemplateTarData() ([]byte, error) {
144100
fork,v:=rangemoduleFiles {
145101
files[k]=v
146102
}
147-
tarData,err:=createTarFromFiles(files)
103+
tarData,err:=loadtestutil.CreateTarFromFiles(files)
148104
iferr!=nil {
149105
returnnil,xerrors.Errorf("failed to create tarball: %w",err)
150106
}

‎scaletest/loadtestutil/files.go‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package loadtestutil
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"path/filepath"
7+
"slices"
8+
)
9+
10+
funcCreateTarFromFiles(filesmap[string][]byte) ([]byte,error) {
11+
buf:=new(bytes.Buffer)
12+
writer:=tar.NewWriter(buf)
13+
dirs:= []string{}
14+
forname,content:=rangefiles {
15+
// We need to add directories before any files that use them. But, we only need to do this
16+
// once.
17+
dir:=filepath.Dir(name)
18+
ifdir!="."&&!slices.Contains(dirs,dir) {
19+
dirs=append(dirs,dir)
20+
err:=writer.WriteHeader(&tar.Header{
21+
Name:dir,
22+
Mode:0o755,
23+
Typeflag:tar.TypeDir,
24+
})
25+
iferr!=nil {
26+
returnnil,err
27+
}
28+
}
29+
30+
err:=writer.WriteHeader(&tar.Header{
31+
Name:name,
32+
Size:int64(len(content)),
33+
Mode:0o644,
34+
})
35+
iferr!=nil {
36+
returnnil,err
37+
}
38+
39+
_,err=writer.Write(content)
40+
iferr!=nil {
41+
returnnil,err
42+
}
43+
}
44+
// `writer.Close()` function flushes the writer buffer, and adds extra padding to create a legal tarball.
45+
err:=writer.Close()
46+
iferr!=nil {
47+
returnnil,err
48+
}
49+
returnbuf.Bytes(),nil
50+
}

‎scaletest/prebuilds/config.go‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package prebuilds
2+
3+
import (
4+
"sync"
5+
"time"
6+
7+
"github.com/google/uuid"
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/quartz"
11+
)
12+
13+
typeConfigstruct {
14+
// OrganizationID is the ID of the organization to create the prebuilds in.
15+
OrganizationID uuid.UUID`json:"organization_id"`
16+
// NumPresets is the number of presets the template should have.
17+
NumPresetsint`json:"num_presets"`
18+
// NumPresetPrebuilds is the number of prebuilds per preset.
19+
// Total prebuilds = NumPresets * NumPresetPrebuilds
20+
NumPresetPrebuildsint`json:"num_preset_prebuilds"`
21+
22+
// TemplateVersionJobTimeout is how long to wait for template version
23+
// provisioning jobs to complete.
24+
TemplateVersionJobTimeout time.Duration`json:"template_version_job_timeout"`
25+
26+
// PrebuildWorkspaceTimeout is how long to wait for all prebuild
27+
// workspaces to be created and completed.
28+
PrebuildWorkspaceTimeout time.Duration`json:"prebuild_workspace_timeout"`
29+
30+
Metrics*Metrics`json:"-"`
31+
32+
// SetupBarrier is used to ensure all templates have been created
33+
// before unpausing prebuilds.
34+
SetupBarrier*sync.WaitGroup`json:"-"`
35+
36+
// DeletionBarrier is used to ensure all templates have been updated
37+
// with 0 prebuilds before resuming prebuilds.
38+
DeletionBarrier*sync.WaitGroup`json:"-"`
39+
40+
Clock quartz.Clock`json:"-"`
41+
}
42+
43+
func (cConfig)Validate()error {
44+
ifc.TemplateVersionJobTimeout<=0 {
45+
returnxerrors.New("template_version_job_timeout must be greater than 0")
46+
}
47+
48+
ifc.PrebuildWorkspaceTimeout<=0 {
49+
returnxerrors.New("prebuild_workspace_timeout must be greater than 0")
50+
}
51+
52+
ifc.SetupBarrier==nil {
53+
returnxerrors.New("setup barrier must be set")
54+
}
55+
56+
ifc.DeletionBarrier==nil {
57+
returnxerrors.New("deletion barrier must be set")
58+
}
59+
60+
ifc.Metrics==nil {
61+
returnxerrors.New("metrics must be set")
62+
}
63+
64+
ifc.Clock==nil {
65+
returnxerrors.New("clock must be set")
66+
}
67+
68+
returnnil
69+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp