Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/test/tests/components/ws-daemon/cpu_burst_test.go
2501 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package wsdaemon
6
7
import (
8
"context"
9
"encoding/json"
10
"strings"
11
"testing"
12
"time"
13
14
"sigs.k8s.io/e2e-framework/pkg/envconf"
15
"sigs.k8s.io/e2e-framework/pkg/features"
16
17
csapi "github.com/gitpod-io/gitpod/content-service/api"
18
daemon "github.com/gitpod-io/gitpod/test/pkg/agent/daemon/api"
19
wsapi "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api"
20
"github.com/gitpod-io/gitpod/test/pkg/integration"
21
wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"
22
corev1 "k8s.io/api/core/v1"
23
"k8s.io/apimachinery/pkg/api/resource"
24
)
25
26
type DaemonConfig struct {
27
CpuLimitConfig struct {
28
Enabled bool `json:"enabled"`
29
Limit int64 `json:"limit,string"`
30
BurstLimit int64 `json:"burstLimit,string"`
31
} `json:"cpuLimit"`
32
IOLimitConfig struct {
33
WriteBandwidthPerSecond resource.Quantity `json:"writeBandwidthPerSecond"`
34
ReadBandwidthPerSecond resource.Quantity `json:"readBandwidthPerSecond"`
35
WriteIOPS int64 `json:"writeIOPS"`
36
ReadIOPS int64 `json:"readIOPS"`
37
} `json:"ioLimit"`
38
}
39
40
func TestCpuBurst(t *testing.T) {
41
f := features.New("cpulimiting").WithLabel("component", "ws-daemon").Assess("check cpu limiting", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
42
t.Parallel()
43
44
ctx, cancel := context.WithTimeout(testCtx, 8*time.Minute)
45
defer cancel()
46
47
api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())
48
t.Cleanup(func() {
49
api.Done(t)
50
})
51
52
daemonConfig := getDaemonConfig(ctx, t, cfg)
53
if !daemonConfig.CpuLimitConfig.Enabled {
54
t.Fatal("cpu limiting is not enabled")
55
}
56
57
if daemonConfig.CpuLimitConfig.Limit == 0 {
58
t.Fatal("cpu limit is not set")
59
}
60
if daemonConfig.CpuLimitConfig.BurstLimit == 0 {
61
t.Fatal("cpu burst limit is not set")
62
}
63
daemonConfig.CpuLimitConfig.Limit = daemonConfig.CpuLimitConfig.Limit * 100_000
64
daemonConfig.CpuLimitConfig.BurstLimit = daemonConfig.CpuLimitConfig.BurstLimit * 100_000
65
66
swr := func(req *wsmanapi.StartWorkspaceRequest) error {
67
req.Spec.Initializer = &csapi.WorkspaceInitializer{
68
Spec: &csapi.WorkspaceInitializer_Git{
69
Git: &csapi.GitInitializer{
70
RemoteUri: "https://github.com/gitpod-io/empty",
71
CheckoutLocation: "empty",
72
Config: &csapi.GitConfig{},
73
},
74
},
75
}
76
77
req.Spec.WorkspaceLocation = "empty"
78
return nil
79
}
80
81
ws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(swr))
82
if err != nil {
83
t.Fatal(err)
84
}
85
defer func() {
86
sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute)
87
defer scancel()
88
89
sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client())
90
defer sapi.Done(t)
91
92
_, err = stopWs(true, sapi)
93
if err != nil {
94
t.Errorf("cannot stop workspace: %q", err)
95
}
96
}()
97
98
daemonClient, daemonCloser, err := integration.Instrument(integration.ComponentWorkspaceDaemon, "daemon", cfg.Namespace(), kubeconfig, cfg.Client(),
99
integration.WithWorkspacekitLift(false),
100
integration.WithContainer("ws-daemon"),
101
)
102
103
if err != nil {
104
t.Fatal(err)
105
}
106
integration.DeferCloser(t, daemonCloser)
107
108
var pod corev1.Pod
109
if err := cfg.Client().Resources().Get(ctx, "ws-"+ws.Req.Id, cfg.Namespace(), &pod); err != nil {
110
t.Fatal(err)
111
}
112
113
containerId := getWorkspaceContainerId(&pod)
114
var resp daemon.GetWorkspaceResourcesResponse
115
for i := 0; i < 10; i++ {
116
err = daemonClient.Call("DaemonAgent.GetWorkspaceResources", daemon.GetWorkspaceResourcesRequest{
117
ContainerId: containerId,
118
}, &resp)
119
120
if resp.CpuQuota == daemonConfig.CpuLimitConfig.Limit {
121
break
122
}
123
time.Sleep(5 * time.Second)
124
}
125
126
if err != nil {
127
t.Fatalf("cannot get workspace resources: %q", err)
128
}
129
130
if resp.CpuQuota != daemonConfig.CpuLimitConfig.Limit {
131
t.Fatalf("expected cpu limit quota of %v, but was %v", daemonConfig.CpuLimitConfig.Limit, resp.CpuQuota)
132
}
133
134
workspaceClient, workspaceCloser, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),
135
integration.WithInstanceID(ws.Req.Id),
136
integration.WithContainer("workspace"),
137
integration.WithWorkspacekitLift(true),
138
)
139
if err != nil {
140
t.Fatal(err)
141
}
142
integration.DeferCloser(t, workspaceCloser)
143
144
var cpuResp wsapi.BurnCpuResponse
145
go func() {
146
err := workspaceClient.Call("WorkspaceAgent.BurnCpu", &wsapi.BurnCpuRequest{
147
Timeout: 30 * time.Second,
148
Procs: 12,
149
}, &cpuResp)
150
151
if err != nil && err.Error() != "unexpected EOF" {
152
t.Logf("error performing cpu burn: %q", err)
153
}
154
}()
155
156
for i := 0; i < 8; i++ {
157
err = daemonClient.Call("DaemonAgent.GetWorkspaceResources", daemon.GetWorkspaceResourcesRequest{
158
ContainerId: containerId,
159
}, &resp)
160
161
if resp.CpuQuota == daemonConfig.CpuLimitConfig.BurstLimit {
162
break
163
}
164
time.Sleep(5 * time.Second)
165
}
166
167
if err != nil {
168
t.Fatalf("cannot get workspace resources: %q", err)
169
}
170
171
if resp.CpuQuota != daemonConfig.CpuLimitConfig.BurstLimit {
172
t.Fatalf("expected cpu burst limit quota of %v, but was %v", daemonConfig.CpuLimitConfig.BurstLimit, resp.CpuQuota)
173
}
174
return testCtx
175
}).Feature()
176
177
testEnv.Test(t, f)
178
}
179
180
func getDaemonConfig(ctx context.Context, t *testing.T, cfg *envconf.Config) DaemonConfig {
181
var daemonConfigMap corev1.ConfigMap
182
183
if err := cfg.Client().Resources().Get(ctx, "ws-daemon", cfg.Namespace(), &daemonConfigMap); err != nil {
184
t.Fatal(err)
185
}
186
187
data, ok := daemonConfigMap.Data["config.json"]
188
if !ok {
189
t.Fatal("server config map does not contain config.json")
190
}
191
192
config := make(map[string]json.RawMessage)
193
if err := json.Unmarshal([]byte(data), &config); err != nil {
194
t.Fatal(err)
195
}
196
197
var daemonConfig DaemonConfig
198
if err := json.Unmarshal(config["daemon"], &daemonConfig); err != nil {
199
t.Fatal(err)
200
}
201
202
return daemonConfig
203
}
204
205
func getWorkspaceContainerId(pod *corev1.Pod) string {
206
for _, c := range pod.Status.ContainerStatuses {
207
if c.Name != "workspace" {
208
continue
209
}
210
211
return strings.TrimPrefix(c.ContainerID, "containerd://")
212
}
213
214
return ""
215
}
216
217