Path: blob/main/test/tests/components/ws-daemon/cpu_burst_test.go
2501 views
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package wsdaemon56import (7"context"8"encoding/json"9"strings"10"testing"11"time"1213"sigs.k8s.io/e2e-framework/pkg/envconf"14"sigs.k8s.io/e2e-framework/pkg/features"1516csapi "github.com/gitpod-io/gitpod/content-service/api"17daemon "github.com/gitpod-io/gitpod/test/pkg/agent/daemon/api"18wsapi "github.com/gitpod-io/gitpod/test/pkg/agent/workspace/api"19"github.com/gitpod-io/gitpod/test/pkg/integration"20wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"21corev1 "k8s.io/api/core/v1"22"k8s.io/apimachinery/pkg/api/resource"23)2425type DaemonConfig struct {26CpuLimitConfig struct {27Enabled bool `json:"enabled"`28Limit int64 `json:"limit,string"`29BurstLimit int64 `json:"burstLimit,string"`30} `json:"cpuLimit"`31IOLimitConfig struct {32WriteBandwidthPerSecond resource.Quantity `json:"writeBandwidthPerSecond"`33ReadBandwidthPerSecond resource.Quantity `json:"readBandwidthPerSecond"`34WriteIOPS int64 `json:"writeIOPS"`35ReadIOPS int64 `json:"readIOPS"`36} `json:"ioLimit"`37}3839func TestCpuBurst(t *testing.T) {40f := features.New("cpulimiting").WithLabel("component", "ws-daemon").Assess("check cpu limiting", func(testCtx context.Context, t *testing.T, cfg *envconf.Config) context.Context {41t.Parallel()4243ctx, cancel := context.WithTimeout(testCtx, 8*time.Minute)44defer cancel()4546api := integration.NewComponentAPI(ctx, cfg.Namespace(), kubeconfig, cfg.Client())47t.Cleanup(func() {48api.Done(t)49})5051daemonConfig := getDaemonConfig(ctx, t, cfg)52if !daemonConfig.CpuLimitConfig.Enabled {53t.Fatal("cpu limiting is not enabled")54}5556if daemonConfig.CpuLimitConfig.Limit == 0 {57t.Fatal("cpu limit is not set")58}59if daemonConfig.CpuLimitConfig.BurstLimit == 0 {60t.Fatal("cpu burst limit is not set")61}62daemonConfig.CpuLimitConfig.Limit = daemonConfig.CpuLimitConfig.Limit * 100_00063daemonConfig.CpuLimitConfig.BurstLimit = daemonConfig.CpuLimitConfig.BurstLimit * 100_0006465swr := func(req *wsmanapi.StartWorkspaceRequest) error {66req.Spec.Initializer = &csapi.WorkspaceInitializer{67Spec: &csapi.WorkspaceInitializer_Git{68Git: &csapi.GitInitializer{69RemoteUri: "https://github.com/gitpod-io/empty",70CheckoutLocation: "empty",71Config: &csapi.GitConfig{},72},73},74}7576req.Spec.WorkspaceLocation = "empty"77return nil78}7980ws, stopWs, err := integration.LaunchWorkspaceDirectly(t, ctx, api, integration.WithRequestModifier(swr))81if err != nil {82t.Fatal(err)83}84defer func() {85sctx, scancel := context.WithTimeout(context.Background(), 5*time.Minute)86defer scancel()8788sapi := integration.NewComponentAPI(sctx, cfg.Namespace(), kubeconfig, cfg.Client())89defer sapi.Done(t)9091_, err = stopWs(true, sapi)92if err != nil {93t.Errorf("cannot stop workspace: %q", err)94}95}()9697daemonClient, daemonCloser, err := integration.Instrument(integration.ComponentWorkspaceDaemon, "daemon", cfg.Namespace(), kubeconfig, cfg.Client(),98integration.WithWorkspacekitLift(false),99integration.WithContainer("ws-daemon"),100)101102if err != nil {103t.Fatal(err)104}105integration.DeferCloser(t, daemonCloser)106107var pod corev1.Pod108if err := cfg.Client().Resources().Get(ctx, "ws-"+ws.Req.Id, cfg.Namespace(), &pod); err != nil {109t.Fatal(err)110}111112containerId := getWorkspaceContainerId(&pod)113var resp daemon.GetWorkspaceResourcesResponse114for i := 0; i < 10; i++ {115err = daemonClient.Call("DaemonAgent.GetWorkspaceResources", daemon.GetWorkspaceResourcesRequest{116ContainerId: containerId,117}, &resp)118119if resp.CpuQuota == daemonConfig.CpuLimitConfig.Limit {120break121}122time.Sleep(5 * time.Second)123}124125if err != nil {126t.Fatalf("cannot get workspace resources: %q", err)127}128129if resp.CpuQuota != daemonConfig.CpuLimitConfig.Limit {130t.Fatalf("expected cpu limit quota of %v, but was %v", daemonConfig.CpuLimitConfig.Limit, resp.CpuQuota)131}132133workspaceClient, workspaceCloser, err := integration.Instrument(integration.ComponentWorkspace, "workspace", cfg.Namespace(), kubeconfig, cfg.Client(),134integration.WithInstanceID(ws.Req.Id),135integration.WithContainer("workspace"),136integration.WithWorkspacekitLift(true),137)138if err != nil {139t.Fatal(err)140}141integration.DeferCloser(t, workspaceCloser)142143var cpuResp wsapi.BurnCpuResponse144go func() {145err := workspaceClient.Call("WorkspaceAgent.BurnCpu", &wsapi.BurnCpuRequest{146Timeout: 30 * time.Second,147Procs: 12,148}, &cpuResp)149150if err != nil && err.Error() != "unexpected EOF" {151t.Logf("error performing cpu burn: %q", err)152}153}()154155for i := 0; i < 8; i++ {156err = daemonClient.Call("DaemonAgent.GetWorkspaceResources", daemon.GetWorkspaceResourcesRequest{157ContainerId: containerId,158}, &resp)159160if resp.CpuQuota == daemonConfig.CpuLimitConfig.BurstLimit {161break162}163time.Sleep(5 * time.Second)164}165166if err != nil {167t.Fatalf("cannot get workspace resources: %q", err)168}169170if resp.CpuQuota != daemonConfig.CpuLimitConfig.BurstLimit {171t.Fatalf("expected cpu burst limit quota of %v, but was %v", daemonConfig.CpuLimitConfig.BurstLimit, resp.CpuQuota)172}173return testCtx174}).Feature()175176testEnv.Test(t, f)177}178179func getDaemonConfig(ctx context.Context, t *testing.T, cfg *envconf.Config) DaemonConfig {180var daemonConfigMap corev1.ConfigMap181182if err := cfg.Client().Resources().Get(ctx, "ws-daemon", cfg.Namespace(), &daemonConfigMap); err != nil {183t.Fatal(err)184}185186data, ok := daemonConfigMap.Data["config.json"]187if !ok {188t.Fatal("server config map does not contain config.json")189}190191config := make(map[string]json.RawMessage)192if err := json.Unmarshal([]byte(data), &config); err != nil {193t.Fatal(err)194}195196var daemonConfig DaemonConfig197if err := json.Unmarshal(config["daemon"], &daemonConfig); err != nil {198t.Fatal(err)199}200201return daemonConfig202}203204func getWorkspaceContainerId(pod *corev1.Pod) string {205for _, c := range pod.Status.ContainerStatuses {206if c.Name != "workspace" {207continue208}209210return strings.TrimPrefix(c.ContainerID, "containerd://")211}212213return ""214}215216217