Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 6 additions & 21 deletions api/types/load_traffic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
package types

import (
"encoding/json"
"fmt"
"strings"

apitypes "k8s.io/apimachinery/pkg/types"
)
Expand Down Expand Up @@ -165,10 +163,8 @@ type RequestPatch struct {
Name string `json:"name" yaml:"name"`
// KeySpaceSize is used to generate random number as name's suffix.
KeySpaceSize int `json:"keySpaceSize" yaml:"keySpaceSize"`
// PatchType is the type of patch, e.g. "json", "merge", "strategic-merge".
PatchType string `json:"patchType" yaml:"patchType"`
// Body is the request body, for fields to be changed.
Body string `json:"body" yaml:"body"`
// ValueSize is the object's size in bytes. how many bytes to patch data
ValueSize int `json:"valueSize" yaml:"valueSize"`
}

// RequestGetPodLog defines GetLog request for target pod.
Expand Down Expand Up @@ -362,24 +358,13 @@ func (r *RequestPatch) Validate() error {
if r.Name == "" {
return fmt.Errorf("name is required")
}
if r.Body == "" {
return fmt.Errorf("body is required")
}

// Validate patch type
_, ok := GetPatchType(r.PatchType)
if !ok {
return fmt.Errorf("unknown patch type: %s (valid types: json, merge, strategic-merge)", r.PatchType)
if r.Resource == "" {
return fmt.Errorf("resource is required")
}

// Validate JSON body and trim it
trimmed := strings.TrimSpace(r.Body)
if !json.Valid([]byte(trimmed)) {
return fmt.Errorf("invalid JSON in patch body: %q", r.Body)
if (r.Resource == "configmaps" || r.Resource == "secrets") && r.ValueSize <= 0 {
return fmt.Errorf("valueSize must > 0 for configmaps and secrets, to generate data to be patched")
}

r.Body = trimmed // Store the trimmed body

return nil
}

Expand Down
21 changes: 21 additions & 0 deletions contrib/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ package utils
import (
"bytes"
"context"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"math/big"
"net"
"os"
"sort"
Expand Down Expand Up @@ -39,8 +41,27 @@ var (
// provider ID for all the virtual nodes so that EKS cloud provider
// won't delete our virtual nodes.
EKSIdleNodepoolInstanceType = "m4.large"
// letterRunes contains the alphabet for random string generation
letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
)

// randString generates a random string of specified length
func RandString(n int) (string, error) {
if n <= 0 {
return "", fmt.Errorf("length must be positive")
}

b := make([]rune, n)
for i := range b {
random, err := rand.Int(rand.Reader, big.NewInt(int64(len(letterRunes))))
if err != nil {
return "", fmt.Errorf("error generating random number: %w", err)
}
b[i] = letterRunes[int(random.Int64())]
}
return string(b), nil
}

// RepeatJobWithPod repeats to deploy 3k pods.
func RepeatJobWithPod(ctx context.Context, kubeCfgPath string, namespace string,
target string, timeoutOpts ...JobTimeoutOpt) {
Expand Down
52 changes: 29 additions & 23 deletions request/random.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func NewWeightedRandomRequests(spec *types.LoadProfileSpec) (*WeightedRandomRequ
case r.GetPodLog != nil:
builder = newRequestGetPodLogBuilder(r.GetPodLog, spec.MaxRetries)
case r.Patch != nil:
builder = newRequestPatchBuilder(r.Patch, "", spec.MaxRetries)
builder = newRequestPatchBuilder(r.Patch, spec.MaxRetries)
case r.PostDel != nil:
builder = newRequestPostDelBuilder(r.PostDel, "", spec.MaxRetries)
default:
Expand Down Expand Up @@ -363,33 +363,27 @@ func (b *requestGetPodLogBuilder) Build(cli rest.Interface) Requester {
}

type requestPatchBuilder struct {
version schema.GroupVersion
resource string
resourceVersion string
namespace string
name string
keySpaceSize int
patchType apitypes.PatchType
body interface{}
maxRetries int
version schema.GroupVersion
resource string
namespace string
name string
keySpaceSize int
valueSize int
maxRetries int
}

func newRequestPatchBuilder(src *types.RequestPatch, resourceVersion string, maxRetries int) *requestPatchBuilder {
patchType, _ := types.GetPatchType(src.PatchType)

func newRequestPatchBuilder(src *types.RequestPatch, maxRetries int) *requestPatchBuilder {
return &requestPatchBuilder{
version: schema.GroupVersion{
Group: src.Group,
Version: src.Version,
},
resource: src.Resource,
resourceVersion: resourceVersion,
namespace: src.Namespace,
name: src.Name,
keySpaceSize: src.KeySpaceSize,
patchType: patchType,
body: []byte(src.Body),
maxRetries: maxRetries,
resource: src.Resource,
namespace: src.Namespace,
name: src.Name,
keySpaceSize: src.KeySpaceSize,
valueSize: src.ValueSize,
maxRetries: maxRetries,
}
}

Expand All @@ -413,11 +407,23 @@ func (b *requestPatchBuilder) Build(cli rest.Interface) Requester {
finalName := fmt.Sprintf("%s-%d", b.name, suffix)
comps = append(comps, b.resource, finalName)

var body []byte

// For configmapas and secrets: generate data based on valueSize
if b.resource == "configmaps" || b.resource == "secrets" && b.valueSize > 0 {
randomData, _ := utils.RandString(b.valueSize)

body = []byte(fmt.Sprintf(`{"data":{"data-key":"%s"}}`, randomData))
} else {
// For other resources: patch based on simple data annotations
body = []byte(fmt.Sprintf(`{"metadata":{"annotations":{"force-update":"%d-%d"}}}`, suffix, time.Now().UnixNano()))
}

return &DiscardRequester{
BaseRequester: BaseRequester{
method: "PATCH",
req: cli.Patch(b.patchType).AbsPath(comps...).
Body(b.body).
req: cli.Patch(apitypes.MergePatchType).AbsPath(comps...).
Body(body).
MaxRetries(b.maxRetries),
},
}
Expand Down
Loading