diff --git a/api/types/load_traffic.go b/api/types/load_traffic.go index d5d9a3b..d14dbf7 100644 --- a/api/types/load_traffic.go +++ b/api/types/load_traffic.go @@ -4,9 +4,7 @@ package types import ( - "encoding/json" "fmt" - "strings" apitypes "k8s.io/apimachinery/pkg/types" ) @@ -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. @@ -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 } diff --git a/contrib/utils/utils.go b/contrib/utils/utils.go index 92c6718..19c66e3 100644 --- a/contrib/utils/utils.go +++ b/contrib/utils/utils.go @@ -6,9 +6,11 @@ package utils import ( "bytes" "context" + "crypto/rand" "encoding/json" "errors" "fmt" + "math/big" "net" "os" "sort" @@ -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) { diff --git a/request/random.go b/request/random.go index 32012e0..6324c8d 100644 --- a/request/random.go +++ b/request/random.go @@ -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: @@ -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, } } @@ -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), }, }