Skip to content

Commit 9e3d709

Browse files
committed
add workspace creation load-test
On-behalf-of: SAP <simon.bein@sap.com> Signed-off-by: Simon Bein <simontheleg@gmail.com>
1 parent 4356356 commit 9e3d709

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
Copyright 2026 The kcp Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package workspace
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"testing"
24+
"time"
25+
26+
"github.com/stretchr/testify/require"
27+
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/util/wait"
30+
31+
"github.com/kcp-dev/sdk/apis/core"
32+
corev1alpha1kcp "github.com/kcp-dev/sdk/apis/core/v1alpha1"
33+
tenancyv1alpha1 "github.com/kcp-dev/sdk/apis/tenancy/v1alpha1"
34+
kcpclientset "github.com/kcp-dev/sdk/client/clientset/versioned/cluster"
35+
36+
"github.com/kcp-dev/kcp/test/load/pkg/framework"
37+
"github.com/kcp-dev/kcp/test/load/pkg/measurement"
38+
"github.com/kcp-dev/kcp/test/load/pkg/stats"
39+
"github.com/kcp-dev/kcp/test/load/pkg/tree"
40+
"github.com/kcp-dev/kcp/test/load/pkg/tuningset"
41+
)
42+
43+
const workspaceCount = 10000
44+
const workspaceDepth = 5
45+
const createWorkspaceQPS = 2.0
46+
47+
func TestWorkspaceCreation(t *testing.T) {
48+
cfg := framework.Require(t, framework.KCPFrontProxyKubeconfig)
49+
50+
client, err := kcpclientset.NewForConfig(cfg.FrontProxyKubeconfig)
51+
require.NoError(t, err)
52+
53+
sections := []measurement.Section{} //nolint:prealloc
54+
55+
createSection := createWorkspaces(t, client, createWorkspaceQPS)
56+
sections = append(sections, createSection)
57+
58+
report := NewKCPReport(t, "Workspace Creation", cfg.FrontProxyKubeconfig)
59+
report.Sections = sections
60+
report.PrettyPrint(os.Stdout)
61+
62+
for _, sec := range sections {
63+
require.Empty(t, sec.Errors, "section %q encountered errors", sec.Title)
64+
}
65+
}
66+
67+
// createWorkspaces creates workspaceCount workspaces under the root workspace
68+
// and waits for each to become Ready.
69+
func createWorkspaces(t *testing.T, client kcpclientset.ClusterInterface, qps float64) measurement.Section {
70+
t.Helper()
71+
wt := defaultTree()
72+
73+
section := measurement.Section{
74+
Title: "Workspace Creation",
75+
Parameters: []measurement.Parameter{
76+
{Key: "WorkspaceTree", Value: wt.String()},
77+
{Key: "QPS", Value: fmt.Sprintf("%f", qps)},
78+
},
79+
Sink: &measurement.Memory{
80+
Stats: []stats.NamedStat{stats.P99(), stats.Avg()},
81+
},
82+
}
83+
84+
ts := tuningset.NewUniformQPS(qps, workspaceCount, 1)
85+
section.Start()
86+
action := func(seq int, s measurement.Sink) error {
87+
defer measurement.RecordElapsedDurationMS(time.Now(), s)
88+
89+
ctx := context.Background()
90+
wsName := wt.WorkspaceName(seq)
91+
92+
ws := &tenancyv1alpha1.Workspace{
93+
ObjectMeta: metav1.ObjectMeta{
94+
Name: wsName,
95+
},
96+
}
97+
98+
// create a client scoped to the parent of the workspace to be created
99+
parent := wt.PathForSequenceNumber(wt.ParentSequenceNumber(seq))
100+
wsClient := client.Cluster(parent).TenancyV1alpha1().Workspaces()
101+
102+
_, err := wsClient.Create(ctx, ws, metav1.CreateOptions{})
103+
if err != nil {
104+
return fmt.Errorf("create workspace: %w", err)
105+
}
106+
107+
// Poll until the workspace reaches the Ready phase.
108+
err = wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, 2*time.Minute, true, func(ctx context.Context) (bool, error) {
109+
got, err := wsClient.Get(ctx, wsName, metav1.GetOptions{})
110+
if err != nil {
111+
// on errors we want to retry
112+
return false, nil //nolint:nilerr
113+
}
114+
return got.Status.Phase == corev1alpha1kcp.LogicalClusterPhaseReady, nil
115+
})
116+
if err != nil {
117+
return fmt.Errorf("workspace did not become ready: %w", err)
118+
}
119+
120+
return nil
121+
}
122+
123+
section.Errors = framework.Execute(ts, action, section.Sink)
124+
section.End()
125+
126+
return section
127+
}
128+
129+
func defaultTree() tree.WorkspaceTree {
130+
return tree.NewSymmetricTree(core.RootCluster.Path(), workspaceCount, workspaceDepth)
131+
}

0 commit comments

Comments
 (0)